Ought to use heap_multi_insert() for pg_attribute/depend insertions?

Started by Andres Freundalmost 7 years ago46 messages
#1Andres Freund
andres@anarazel.de

Hi,

Turns out in portions of the regression tests a good chunk of the
runtime is inside AddNewAttributeTuples() and
recordMultipleDependencies()'s heap insertions. Looking at a few
profiles I had lying around I found that in some production cases
too. ISTM we should use heap_multi_insert() for both, as the source
tuples ought to be around reasonably comfortably.

For recordMultipleDependencies() it'd obviously better if we collected
all dependencies for new objects, rather than doing so separately. Right
now e.g. the code for a new table looks like:

recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

recordDependencyOnOwner(RelationRelationId, relid, ownerid);

recordDependencyOnNewAcl(RelationRelationId, relid, 0, ownerid, relacl);

recordDependencyOnCurrentExtension(&myself, false);

if (reloftypeid)
{
referenced.classId = TypeRelationId;
referenced.objectId = reloftypeid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}

and it'd obviously be more efficient to do that once if we went to using
heap_multi_insert() in the dependency code. But I suspect even if we
just used an extended API in AddNewAttributeTuples() (for the type /
collation dependencies), it'd be a win.

I'm not planning to work on this soon, but I though it'd be worthwhile
to put this out there (even if potentially just as a note to myself).

Greetings,

Andres Freund

#2Daniel Gustafsson
daniel@yesql.se
In reply to: Andres Freund (#1)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 13 Feb 2019, at 19:27, Andres Freund <andres@anarazel.de> wrote:

Hi,

Turns out in portions of the regression tests a good chunk of the
runtime is inside AddNewAttributeTuples() and
recordMultipleDependencies()'s heap insertions. Looking at a few
profiles I had lying around I found that in some production cases
too. ISTM we should use heap_multi_insert() for both, as the source
tuples ought to be around reasonably comfortably.

For recordMultipleDependencies() it'd obviously better if we collected
all dependencies for new objects, rather than doing so separately. Right
now e.g. the code for a new table looks like:

recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

recordDependencyOnOwner(RelationRelationId, relid, ownerid);

recordDependencyOnNewAcl(RelationRelationId, relid, 0, ownerid, relacl);

recordDependencyOnCurrentExtension(&myself, false);

if (reloftypeid)
{
referenced.classId = TypeRelationId;
referenced.objectId = reloftypeid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}

and it'd obviously be more efficient to do that once if we went to using
heap_multi_insert() in the dependency code. But I suspect even if we
just used an extended API in AddNewAttributeTuples() (for the type /
collation dependencies), it'd be a win.

When a colleague was looking at heap_multi_insert in the COPY codepath I
remembered this and took a stab at a WIP patch inspired by this email, while
not following it to the letter. It’s not going the full route of collecting
all the dependencies for creating a table, but adding ways to perform
multi_heap_insert in the existing codepaths as it seemed like a good place to
start.

It introduces a new function CatalogMultiInsertWithInfo which takes a set of
slots for use in heap_multi_insert, used from recordMultipleDependencies and
InsertPgAttributeTuples (which replace calling InsertPgAttributeTuple
repeatedly). The code is still a WIP with some kludges, following the show-
early philosophy.

It passes make check and some light profiling around regress suites indicates
that it does improve a bit by reducing the somewhat costly calls. Is this
along the lines of what you were thinking or way off?

cheers ./daniel

Attachments:

catalog_multi_insert.patchapplication/octet-stream; name=catalog_multi_insert.patch; x-unix-mode=0644Download
From b4a535b34636de168166332b03cd8c7680feabe2 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 22 May 2019 09:59:55 +0200
Subject: [PATCH] WIP: Use heap_multi_insert for catalog relations

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/catalog/heap.c          | 125 ++++++++++++++++++++++++++++++------
 src/backend/catalog/indexing.c      |  37 +++++++++++
 src/backend/catalog/pg_constraint.c |  40 ++++++++----
 src/backend/catalog/pg_depend.c     |  76 +++++++++++++++++++---
 src/backend/catalog/pg_shdepend.c   |  26 ++++++--
 src/include/catalog/indexing.h      |   3 +
 6 files changed, 257 insertions(+), 50 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 6cffe550b3..281ec92f61 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -126,6 +126,10 @@ static Node *cookConstraint(ParseState *pstate,
 			   Node *raw_constraint,
 			   char *relname);
 static List *insert_ordered_unique_oid(List *list, Oid datum);
+static void InsertPgAttributeTuples(Relation pg_attribute_rel,
+						FormData_pg_attribute *new_attributes,
+						int natts,
+						CatalogIndexState indstate);
 
 
 /* ----------------------------------------------------------------
@@ -678,6 +682,78 @@ CheckAttributeType(const char *attname,
 				 errhint("Use the COLLATE clause to set the collation explicitly.")));
 }
 
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
+ *
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
+ */
+static void
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						FormData_pg_attribute *new_attributes,
+						int natts,
+						CatalogIndexState indstate)
+{
+	Datum		values[Natts_pg_attribute];
+	bool		nulls[Natts_pg_attribute];
+	HeapTuple	tup;
+	int			i;
+	TupleTableSlot **slot;
+
+	/*
+	 * The slots are dropped in CatalogMultiInsertWithInfo(). TODO Is natts
+	 * number of slots a reasonable assumption?
+	 */
+	slot = palloc(sizeof(TupleTableSlot *) * natts);
+
+	/* This is a tad tedious, but way cleaner than what we used to do... */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	/* start out with empty permissions and empty options */
+	nulls[Anum_pg_attribute_attacl - 1] = true;
+	nulls[Anum_pg_attribute_attoptions - 1] = true;
+	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
+	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+
+	/* attcacheoff is always -1 in storage */
+	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+
+	for (i = 0; i < natts; i++)
+	{
+		values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes[i].attrelid);
+		values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes[i].attname);
+		values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes[i].atttypid);
+		values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes[i].attstattarget);
+		values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes[i].attlen);
+		values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes[i].attnum);
+		values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes[i].attndims);
+		values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes[i].atttypmod);
+		values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes[i].attbyval);
+		values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes[i].attstorage);
+		values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes[i].attalign);
+		values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes[i].attnotnull);
+		values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes[i].atthasdef);
+		values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes[i].atthasmissing);
+		values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes[i].attidentity);
+		values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes[i].attgenerated);
+		values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes[i].attisdropped);
+		values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes[i].attislocal);
+		values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes[i].attinhcount);
+		values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes[i].attcollation);
+
+		slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_attribute_rel),
+										   &TTSOpsHeapTuple);
+		tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecStoreHeapTuple(heap_copytuple(tup), slot[i], false);
+	}
+
+	/* finally insert the new tuples, update the indexes, and clean up */
+	CatalogMultiInsertWithInfo(pg_attribute_rel, slot, natts, indstate);
+}
+
 /*
  * InsertPgAttributeTuple
  *		Construct and insert a new tuple in pg_attribute.
@@ -772,7 +848,9 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	/*
 	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * add dependencies on their datatypes and collations. Replacing this
+	 * loop with recordMultipleDependenciesOn() would possibly be a good
+	 * optimization, but would require some refactoring.
 	 */
 	for (i = 0; i < natts; i++)
 	{
@@ -811,17 +889,16 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
+		FormData_pg_attribute attStructs[lengthof(SysAtt)];
+
+		/* Fill in the correct relation OID in the copied tuple */
 		for (i = 0; i < (int) lengthof(SysAtt); i++)
 		{
-			FormData_pg_attribute attStruct;
-
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
-
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
+			memcpy(&attStructs[i], SysAtt[i], sizeof(FormData_pg_attribute));
+			attStructs[i].attrelid = new_rel_oid;
 		}
+
+		InsertPgAttributeTuples(rel, attStructs, lengthof(SysAtt), indstate);
 	}
 
 	/*
@@ -3459,7 +3536,12 @@ StorePartitionKey(Relation rel,
 	Datum		values[Natts_pg_partitioned_table];
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
-	ObjectAddress referenced;
+	/*
+	 * PARTITION_MAX_KEYS is the upper bound for partnatts, so grabbing it on
+	 * the stack can save us a palloc.
+	 */
+	ObjectAddress referenced[PARTITION_MAX_KEYS];
+	int			nref;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3508,26 +3590,27 @@ StorePartitionKey(Relation rel,
 	myself.objectSubId = 0;
 
 	/* Operator class and collation per key column */
-	for (i = 0; i < partnatts; i++)
+	for (i = 0, nref = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
-
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		referenced[nref].classId = OperatorClassRelationId;
+		referenced[nref].objectId = partopclass[i];
+		referenced[nref].objectSubId = 0;
+		nref++;
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
-
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			referenced[nref].classId = CollationRelationId;
+			referenced[nref].objectId = partcollation[i];
+			referenced[nref].objectSubId = 0;
+			nref++;
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	recordMultipleDependencies(&myself, referenced, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * Anything mentioned in the expressions.  We must ignore the column
 	 * references, which will depend on the table itself; there is no separate
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index f237e62bc9..0819a6449c 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,42 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, int ntuples,
+						   CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+		ExecDropSingleTupleTableSlot(slot[i]);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b6145593a3..91f10ff3ef 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -230,27 +230,35 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	ObjectAddress *relobjects;
+	if (constraintNTotalKeys || foreignNKeys)
+		relobjects = palloc(sizeof(ObjectAddress) * Max(constraintNTotalKeys, foreignNKeys));
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
+			Assert(relobjects);
+
 			for (i = 0; i < constraintNTotalKeys; i++)
 			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+				relobjects[i].classId = RelationRelationId;
+				relobjects[i].objectId = relId;
+				relobjects[i].objectSubId = constraintKey[i];
 			}
+
+			recordMultipleDependencies(&conobject, relobjects, constraintNTotalKeys, DEPENDENCY_AUTO);
 		}
 		else
 		{
+			ObjectAddress relobject;
+
+			relobject.classId = RelationRelationId;
+			relobject.objectId = relId;
 			relobject.objectSubId = 0;
 
 			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
@@ -277,21 +285,25 @@ CreateConstraintEntry(const char *constraintName,
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
+			Assert(relobjects);
+
 			for (i = 0; i < foreignNKeys; i++)
 			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+				relobjects[i].classId = RelationRelationId;
+				relobjects[i].objectId = foreignRelId;
+				relobjects[i].objectSubId = foreignKey[i];
 			}
+
+			recordMultipleDependencies(&conobject, relobjects, foreignNKeys, DEPENDENCY_NORMAL);
 		}
 		else
 		{
+			ObjectAddress relobject;
+
+			relobject.classId = RelationRelationId;
+			relobject.objectId = foreignRelId;
 			relobject.objectSubId = 0;
 
 			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index f7caedcc02..14e0d03c73 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,7 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	HeapTuple	tuple;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
@@ -81,7 +128,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 	memset(nulls, false, sizeof(nulls));
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
+
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -104,20 +154,28 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+
+			tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreHeapTuple(heap_copytuple(tuple), slot[ntuples], false);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 4a9b4efb05..bf49efe099 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -66,7 +66,6 @@
 #include "utils/fmgroids.h"
 #include "utils/syscache.h"
 
-
 typedef enum
 {
 	LOCAL_OBJECT,
@@ -800,10 +799,15 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	HeapTuple	newtuple;
+	int			ntuples;
 	CatalogIndexState indstate;
 	Datum		values[Natts_pg_shdepend];
 	bool		nulls[Natts_pg_shdepend];
 	bool		replace[Natts_pg_shdepend];
+	/* TODO figure out a sensible value for the amount of slots */
+#define	DEPEND_TUPLE_BUF 32
+	TupleTableSlot *slot[DEPEND_TUPLE_BUF];
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
@@ -834,16 +838,26 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	ntuples = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+									&TTSOpsHeapTuple);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(heap_copytuple(newtuple), slot[ntuples], false);
+		ntuples++;
 
-		heap_freetuple(newtup);
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (ntuples)
+		CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index f253613650..8df5dec434 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,8 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 						   CatalogIndexState indstate);
+extern void CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+						   int ntuples, CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 				   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
-- 
2.14.1.145.gb3622a4ee

#3Andres Freund
andres@anarazel.de
In reply to: Daniel Gustafsson (#2)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

Hi,

On 2019-05-22 10:25:14 +0200, Daniel Gustafsson wrote:

On 13 Feb 2019, at 19:27, Andres Freund <andres@anarazel.de> wrote:

Hi,

Turns out in portions of the regression tests a good chunk of the
runtime is inside AddNewAttributeTuples() and
recordMultipleDependencies()'s heap insertions. Looking at a few
profiles I had lying around I found that in some production cases
too. ISTM we should use heap_multi_insert() for both, as the source
tuples ought to be around reasonably comfortably.

For recordMultipleDependencies() it'd obviously better if we collected
all dependencies for new objects, rather than doing so separately. Right
now e.g. the code for a new table looks like:

recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

recordDependencyOnOwner(RelationRelationId, relid, ownerid);

recordDependencyOnNewAcl(RelationRelationId, relid, 0, ownerid, relacl);

recordDependencyOnCurrentExtension(&myself, false);

if (reloftypeid)
{
referenced.classId = TypeRelationId;
referenced.objectId = reloftypeid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}

and it'd obviously be more efficient to do that once if we went to using
heap_multi_insert() in the dependency code. But I suspect even if we
just used an extended API in AddNewAttributeTuples() (for the type /
collation dependencies), it'd be a win.

When a colleague was looking at heap_multi_insert in the COPY codepath I
remembered this and took a stab at a WIP patch inspired by this email, while
not following it to the letter. It’s not going the full route of collecting
all the dependencies for creating a table, but adding ways to perform
multi_heap_insert in the existing codepaths as it seemed like a good place to
start.

Cool. I don't quite have the energy to look at this right now, could you
create a CF entry for this?

Greetings,

Andres Freund

#4Daniel Gustafsson
daniel@yesql.se
In reply to: Andres Freund (#3)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 23 May 2019, at 03:46, Andres Freund <andres@anarazel.de> wrote:
On 2019-05-22 10:25:14 +0200, Daniel Gustafsson wrote:

When a colleague was looking at heap_multi_insert in the COPY codepath I
remembered this and took a stab at a WIP patch inspired by this email, while
not following it to the letter. It’s not going the full route of collecting
all the dependencies for creating a table, but adding ways to perform
multi_heap_insert in the existing codepaths as it seemed like a good place to
start.

Cool. I don't quite have the energy to look at this right now, could you
create a CF entry for this?

Of course, done.

cheers ./daniel

#5Andres Freund
andres@anarazel.de
In reply to: Daniel Gustafsson (#2)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

Hi,

On 2019-05-22 10:25:14 +0200, Daniel Gustafsson wrote:

It passes make check and some light profiling around regress suites indicates
that it does improve a bit by reducing the somewhat costly calls.

Just for the record, here is the profile I did:

perf record --call-graph lbr make -s check-world -Otarget -j16 -s
perf report

Greetings,

Andres Freund

#6Daniel Gustafsson
daniel@yesql.se
In reply to: Andres Freund (#3)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 23 May 2019, at 03:46, Andres Freund <andres@anarazel.de> wrote:
On 2019-05-22 10:25:14 +0200, Daniel Gustafsson wrote:

When a colleague was looking at heap_multi_insert in the COPY codepath I
remembered this and took a stab at a WIP patch inspired by this email, while
not following it to the letter. It’s not going the full route of collecting
all the dependencies for creating a table, but adding ways to perform
multi_heap_insert in the existing codepaths as it seemed like a good place to
start.

Cool. I don't quite have the energy to look at this right now, could you
create a CF entry for this?

Attached is an updated version with some of the stuff we briefly discussed at
PGCon. This version use the ObjectAddresses API already established to collect
the dependencies, and perform a few more multi inserts. Profiling shows that
we are spending less time in catalog insertions, but whether it’s enough to
warrant the added complexity is up for debate.

The patch is still rough around the edges (TODO’s left to mark some areas), but
I prefer to get some feedback early rather than working too far in potentially
the wrong direction, so parking this in the CF for now.

cheers ./daniel

Attachments:

catalog_multi_insert-v2.patchapplication/octet-stream; name=catalog_multi_insert-v2.patch; x-unix-mode=0644Download
From 04a391faf80667aaefa6c589eb323ceb114f7d28 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 22 May 2019 09:59:55 +0200
Subject: [PATCH] WIP: Use heap_multi_insert for catalog relations

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/catalog/dependency.c    |  54 ++++++++++----
 src/backend/catalog/heap.c          | 138 ++++++++++++++++++++++++++++--------
 src/backend/catalog/index.c         |  58 +++++++++------
 src/backend/catalog/indexing.c      |  37 ++++++++++
 src/backend/catalog/pg_aggregate.c  |  69 ++++--------------
 src/backend/catalog/pg_constraint.c |  75 ++++++--------------
 src/backend/catalog/pg_depend.c     |  84 ++++++++++++++++++----
 src/backend/catalog/pg_operator.c   |  58 ++++-----------
 src/backend/catalog/pg_proc.c       |  50 ++++---------
 src/backend/catalog/pg_shdepend.c   |  26 +++++--
 src/backend/catalog/pg_type.c       |  51 ++++++-------
 src/include/catalog/dependency.h    |   5 ++
 src/include/catalog/heap.h          |   4 ++
 src/include/catalog/indexing.h      |   3 +
 14 files changed, 412 insertions(+), 300 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6315fc4b2f..7425bb76e5 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -205,8 +205,6 @@ static bool find_expr_references_walker(Node *node,
 										find_expr_references_context *context);
 static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
 static int	object_address_comparator(const void *a, const void *b);
-static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
-							   ObjectAddresses *addrs);
 static void add_exact_object_address_extra(const ObjectAddress *object,
 										   const ObjectAddressExtra *extra,
 										   ObjectAddresses *addrs);
@@ -1574,10 +1572,16 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	/* Remove any duplicates */
 	eliminate_duplicate_dependencies(context.addrs);
 
-	/* And record 'em */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * And record 'em. If we know we only have a single dependency then
+	 * avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -1666,10 +1670,16 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		free_object_addresses(self_addrs);
 	}
 
-	/* Record the external dependencies */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * Record the external dependencies. If we only have a single dependency
+	 * then avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -2362,7 +2372,7 @@ new_object_addresses(void)
  * It is convenient to specify the class by ObjectClass rather than directly
  * by catalog OID.
  */
-static void
+void
 add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
 				   ObjectAddresses *addrs)
 {
@@ -2610,9 +2620,13 @@ record_object_address_dependencies(const ObjectAddress *depender,
 								   DependencyType behavior)
 {
 	eliminate_duplicate_dependencies(referenced);
-	recordMultipleDependencies(depender,
-							   referenced->refs, referenced->numrefs,
-							   behavior);
+
+	if (referenced->numrefs == 1)
+		recordDependencyOn(depender, &referenced->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   referenced->refs, referenced->numrefs,
+								   behavior);
 }
 
 /*
@@ -2632,6 +2646,18 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 86820eecfc..ba31d87c1f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -678,6 +678,79 @@ CheckAttributeType(const char *attname,
 				 errhint("Use the COLLATE clause to set the collation explicitly.")));
 }
 
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
+ *
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
+ */
+void
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						FormData_pg_attribute *new_attributes,
+						int natts,
+						CatalogIndexState indstate)
+{
+	Datum		values[Natts_pg_attribute];
+	bool		nulls[Natts_pg_attribute];
+	HeapTuple	tup;
+	int			i;
+	TupleTableSlot **slot;
+
+	/*
+	 * The slots are dropped in CatalogMultiInsertWithInfo(). TODO: natts
+	 * number of slots is not a reasonable assumption as a wide relation
+	 * would be detrimental, figuring a good number is left as a TODO.
+	 */
+	slot = palloc(sizeof(TupleTableSlot *) * natts);
+
+	/* This is a tad tedious, but way cleaner than what we used to do... */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	/* start out with empty permissions and empty options */
+	nulls[Anum_pg_attribute_attacl - 1] = true;
+	nulls[Anum_pg_attribute_attoptions - 1] = true;
+	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
+	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+
+	/* attcacheoff is always -1 in storage */
+	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+
+	for (i = 0; i < natts; i++)
+	{
+		values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes[i].attrelid);
+		values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes[i].attname);
+		values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes[i].atttypid);
+		values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes[i].attstattarget);
+		values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes[i].attlen);
+		values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes[i].attnum);
+		values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes[i].attndims);
+		values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes[i].atttypmod);
+		values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes[i].attbyval);
+		values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes[i].attstorage);
+		values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes[i].attalign);
+		values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes[i].attnotnull);
+		values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes[i].atthasdef);
+		values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes[i].atthasmissing);
+		values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes[i].attidentity);
+		values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes[i].attgenerated);
+		values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes[i].attisdropped);
+		values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes[i].attislocal);
+		values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes[i].attinhcount);
+		values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes[i].attcollation);
+
+		slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_attribute_rel),
+										   &TTSOpsHeapTuple);
+		tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecStoreHeapTuple(heap_copytuple(tup), slot[i], false);
+	}
+
+	/* finally insert the new tuples, update the indexes, and clean up */
+	CatalogMultiInsertWithInfo(pg_attribute_rel, slot, natts, indstate);
+}
+
 /*
  * InsertPgAttributeTuple
  *		Construct and insert a new tuple in pg_attribute.
@@ -755,7 +828,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
+	Form_pg_attribute *attrs;
 	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
@@ -770,35 +843,42 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	attrs = palloc(sizeof(Form_pg_attribute) * natts);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * First we add the user attributes.
 	 */
 	for (i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
+		attrs[i] = TupleDescAttr(tupdesc, i);
 		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
+		attrs[i]->attrelid = new_rel_oid;
 		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
+		attrs[i]->attstattarget = -1;
+	}
 
-		InsertPgAttributeTuple(rel, attr, indstate);
+	InsertPgAttributeTuples(rel, *attrs, natts, indstate);
 
+	/*
+	 * Now add dependencies on their datatypes and collations.
+	 */
+	for (i = 0; i < natts; i++)
+	{
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = attrs[i]->atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(attrs[i]->attcollation) &&
+			attrs[i]->attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = attrs[i]->attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -811,22 +891,22 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
+		FormData_pg_attribute attStructs[lengthof(SysAtt)];
+
+		/* Fill in the correct relation OID in the copied tuple */
 		for (i = 0; i < (int) lengthof(SysAtt); i++)
 		{
-			FormData_pg_attribute attStruct;
-
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
-
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
+			memcpy(&attStructs[i], SysAtt[i], sizeof(FormData_pg_attribute));
+			attStructs[i].attrelid = new_rel_oid;
 		}
+
+		InsertPgAttributeTuples(rel, attStructs, lengthof(SysAtt), indstate);
 	}
 
 	/*
 	 * clean up
 	 */
+	pfree(attrs);
 	CatalogCloseIndexes(indstate);
 
 	table_close(rel, RowExclusiveLock);
@@ -3459,7 +3539,7 @@ StorePartitionKey(Relation rel,
 	Datum		values[Natts_pg_partitioned_table];
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
-	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3507,27 +3587,25 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
-
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_OPCLASS, partopclass[i], 0, refobjs);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
-
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_COLLATION, partcollation[i], 0, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * Anything mentioned in the expressions.  We must ignore the column
 	 * references, which will depend on the table itself; there is no separate
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index d2e4f53a80..57b58d6afa 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -505,14 +505,30 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
+	/*
+	 * If we only have a single attribute we can use the single tuple insert
+	 * function which avoids multi_insert scaffolding costs.
+	 */
+	if (numatts == 1)
 	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-
-		Assert(attr->attnum == i + 1);
+		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, 0);
 
 		InsertPgAttributeTuple(pg_attribute, attr, indstate);
 	}
+	else
+	{
+		Form_pg_attribute *attrs = palloc(sizeof(Form_pg_attribute) * numatts);
+
+		for (i = 0; i < numatts; i++)
+		{
+			attrs[i] = TupleDescAttr(indexTupDesc, i);
+
+			Assert(attrs[i]->attnum == i + 1);
+		}
+
+		InsertPgAttributeTuples(pg_attribute, *attrs, numatts, indstate);
+		pfree(attrs);
+	}
 
 	CatalogCloseIndexes(indstate);
 
@@ -982,11 +998,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1025,12 +1044,9 @@ index_create(Relation heapRelation,
 			{
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					add_object_address(OCLASS_CLASS, heapRelationId,
+									   indexInfo->ii_IndexAttrNumbers[i],
+									   refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1049,6 +1065,11 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+			{
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
+				reset_object_addresses(refobjs);
+			}
 		}
 
 		/*
@@ -1079,23 +1100,16 @@ index_create(Relation heapRelation,
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				add_object_address(OCLASS_COLLATION, collationObjectId[i], 0,
+								   refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
-		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			add_object_address(OCLASS_OPCLASS, classObjectId[i], 0, refobjs);
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
@@ -1116,6 +1130,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index f237e62bc9..0819a6449c 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,42 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, int ntuples,
+						   CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+		ExecDropSingleTupleTableSlot(slot[i]);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 7cab039ded..5d3d3e9069 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -104,8 +104,8 @@ AggregateCreate(const char *aggName,
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -740,83 +740,44 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_PROC, transfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, finalfn, 0, refobjs);
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, combinefn, 0, refobjs);
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, serialfn, 0, refobjs);
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, deserialfn, 0, refobjs);
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mtransfn, 0, refobjs);
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, minvtransfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mfinalfn, 0, refobjs);
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
-	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_OPERATOR, sortop, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 	return myself;
 }
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b6145593a3..be7c6b8c12 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -92,6 +92,7 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -230,30 +231,22 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
-			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
-			}
+				add_object_address(OCLASS_CLASS, relId, constraintKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			add_object_address(OCLASS_CLASS, relId, 0, refobjs);
 		}
 	}
 
@@ -262,39 +255,27 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
-
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
+		add_object_address(OCLASS_TYPE, domainId, 0, refobjs);
 	}
 
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
+	reset_object_addresses(refobjs);
+
 	if (OidIsValid(foreignRelId))
 	{
 		/*
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
-			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_CLASS, foreignRelId, foreignKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_CLASS, foreignRelId, 0, refobjs);
 		}
 	}
 
@@ -306,13 +287,7 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_CLASS, indexRelId, 0, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -323,28 +298,22 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_OPERATOR, pfEqOp[i], 0, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ppEqOp[i], 0, refobjs);
+
 			if (ffEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ffEqOp[i], 0, refobjs);
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index f7caedcc02..b9bd2db880 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,7 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	HeapTuple	tuple;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
@@ -81,7 +128,14 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 	memset(nulls, false, sizeof(nulls));
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
+
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -94,30 +148,34 @@ recordMultipleDependencies(const ObjectAddress *depender,
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
-
 			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
 			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
 			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
 
 			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+
+			tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreHeapTuple(heap_copytuple(tuple), slot[ntuples], false);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index bcaa26c997..b37f01df09 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,15 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -792,39 +794,19 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
-	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_SCHEMA, oper->oprnamespace, 0, refobjs);
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprleft, 0, refobjs);
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprright, 0, refobjs);
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprresult, 0, refobjs);
 
 	/*
 	 * NOTE: we do not consider the operator to depend on the associated
@@ -837,30 +819,18 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprcode, 0, refobjs);
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprrest, 0, refobjs);
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprjoin, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 002584b941..7f8fe01651 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -114,8 +114,8 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	int			i;
 	Oid			trfid;
 
@@ -613,49 +613,29 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, procNamespace, 0, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_LANGUAGE, languageObjectId, 0, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_TYPE, returnType, 0, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
-	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_TYPE, allParams[i], 0, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
-		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+			add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 	}
 
 	/* dependency on parameter default expressions */
@@ -665,12 +645,10 @@ ProcedureCreate(const char *procedureName,
 
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, prosupport, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* dependency on owner */
 	if (!is_update)
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index fb7f8ddefc..5a24890d8a 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -66,7 +66,6 @@
 #include "utils/fmgroids.h"
 #include "utils/syscache.h"
 
-
 typedef enum
 {
 	LOCAL_OBJECT,
@@ -800,10 +799,15 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	HeapTuple	newtuple;
+	int			ntuples;
 	CatalogIndexState indstate;
 	Datum		values[Natts_pg_shdepend];
 	bool		nulls[Natts_pg_shdepend];
 	bool		replace[Natts_pg_shdepend];
+	/* TODO figure out a sensible value for the amount of slots */
+#define	DEPEND_TUPLE_BUF 32
+	TupleTableSlot *slot[DEPEND_TUPLE_BUF];
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
@@ -834,16 +838,26 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	ntuples = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+									&TTSOpsHeapTuple);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(heap_copytuple(newtuple), slot[ntuples], false);
+		ntuples++;
 
-		heap_freetuple(newtup);
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (ntuples)
+		CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 2a51501d8d..b3707b0f7e 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -544,7 +544,9 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool rebuild)
 {
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
@@ -579,63 +581,54 @@ GenerateTypeDependencies(Oid typeObjectId,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index ef9c86864c..0a8d3ff613 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -162,6 +162,9 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
 
 extern ObjectAddresses *new_object_addresses(void);
 
+extern void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
+							   ObjectAddresses *addrs);
+
 extern void add_exact_object_address(const ObjectAddress *object,
 									 ObjectAddresses *addrs);
 
@@ -174,6 +177,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index eec71c29d5..24405977ff 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -95,6 +95,10 @@ extern List *heap_truncate_find_FKs(List *relationIds);
 extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
 								   Form_pg_attribute new_attribute,
 								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									FormData_pg_attribute *new_attributes,
+									int natts,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 5f2aee8a4a..a356947367 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,8 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+						   int ntuples, CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
-- 
2.14.1.145.gb3622a4ee

#7Andres Freund
andres@anarazel.de
In reply to: Daniel Gustafsson (#6)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

Hi,

On 2019-06-11 15:20:42 +0200, Daniel Gustafsson wrote:

Attached is an updated version with some of the stuff we briefly discussed at
PGCon. This version use the ObjectAddresses API already established to collect
the dependencies, and perform a few more multi inserts.

Cool.

Profiling shows that
we are spending less time in catalog insertions, but whether it’s enough to
warrant the added complexity is up for debate.

Probably worth benchmarking e.g. temp table creation speed in
isolation. People do complain about that occasionally.

Greetings,

Andres Freund

#8Thomas Munro
thomas.munro@gmail.com
In reply to: Daniel Gustafsson (#6)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Wed, Jun 12, 2019 at 1:21 AM Daniel Gustafsson <daniel@yesql.se> wrote:

The patch is still rough around the edges (TODO’s left to mark some areas), but
I prefer to get some feedback early rather than working too far in potentially
the wrong direction, so parking this in the CF for now.

Hi Daniel,

Given the above disclaimers the following may be entirely expected,
but just in case you weren't aware:
t/010_logical_decoding_timelines.pl fails with this patch applied.

https://travis-ci.org/postgresql-cfbot/postgresql/builds/555205042

--
Thomas Munro
https://enterprisedb.com

#9Daniel Gustafsson
daniel@yesql.se
In reply to: Thomas Munro (#8)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 8 Jul 2019, at 00:02, Thomas Munro <thomas.munro@gmail.com> wrote:

On Wed, Jun 12, 2019 at 1:21 AM Daniel Gustafsson <daniel@yesql.se> wrote:

The patch is still rough around the edges (TODO’s left to mark some areas), but
I prefer to get some feedback early rather than working too far in potentially
the wrong direction, so parking this in the CF for now.

Hi Daniel,

Given the above disclaimers the following may be entirely expected,
but just in case you weren't aware:
t/010_logical_decoding_timelines.pl fails with this patch applied.

https://travis-ci.org/postgresql-cfbot/postgresql/builds/555205042

I hadn’t seen since I had fat-fingered and accidentally run the full tests in a
tree without assertions. The culprit here seems to an assertion in the logical
decoding code which doesn’t account for heap_multi_insert into catalog
relations (which there are none now, this patch introduce them and thus trip
the assertion). As the issue is somewhat unrelated, I’ve opened a separate
thread with a small patch:

/messages/by-id/CBFFD532-C033-49EB-9A5A-F67EAEE9EB0B@yesql.se

The attached v3 also has that fix in order to see if the cfbot is happier with
this.

cheers ./daniel

Attachments:

catalog_multi_insert-v3.patchapplication/octet-stream; name=catalog_multi_insert-v3.patch; x-unix-mode=0644Download
From 37d925e89bbf2764eb749a11f53b082e40577203 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 22 May 2019 09:59:55 +0200
Subject: [PATCH] WIP: Use heap_multi_insert for catalog relations

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/catalog/dependency.c         |  54 ++++++++----
 src/backend/catalog/heap.c               | 138 ++++++++++++++++++++++++-------
 src/backend/catalog/index.c              |  58 ++++++++-----
 src/backend/catalog/indexing.c           |  37 +++++++++
 src/backend/catalog/pg_aggregate.c       |  69 ++++------------
 src/backend/catalog/pg_constraint.c      |  75 +++++------------
 src/backend/catalog/pg_depend.c          |  84 ++++++++++++++++---
 src/backend/catalog/pg_operator.c        |  58 ++++---------
 src/backend/catalog/pg_proc.c            |  50 ++++-------
 src/backend/catalog/pg_shdepend.c        |  26 ++++--
 src/backend/catalog/pg_type.c            |  51 +++++-------
 src/backend/replication/logical/decode.c |   3 +-
 src/include/catalog/dependency.h         |   5 ++
 src/include/catalog/heap.h               |   4 +
 src/include/catalog/indexing.h           |   3 +
 15 files changed, 414 insertions(+), 301 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6315fc4b2f..7425bb76e5 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -205,8 +205,6 @@ static bool find_expr_references_walker(Node *node,
 										find_expr_references_context *context);
 static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
 static int	object_address_comparator(const void *a, const void *b);
-static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
-							   ObjectAddresses *addrs);
 static void add_exact_object_address_extra(const ObjectAddress *object,
 										   const ObjectAddressExtra *extra,
 										   ObjectAddresses *addrs);
@@ -1574,10 +1572,16 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	/* Remove any duplicates */
 	eliminate_duplicate_dependencies(context.addrs);
 
-	/* And record 'em */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * And record 'em. If we know we only have a single dependency then
+	 * avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -1666,10 +1670,16 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		free_object_addresses(self_addrs);
 	}
 
-	/* Record the external dependencies */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * Record the external dependencies. If we only have a single dependency
+	 * then avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -2362,7 +2372,7 @@ new_object_addresses(void)
  * It is convenient to specify the class by ObjectClass rather than directly
  * by catalog OID.
  */
-static void
+void
 add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
 				   ObjectAddresses *addrs)
 {
@@ -2610,9 +2620,13 @@ record_object_address_dependencies(const ObjectAddress *depender,
 								   DependencyType behavior)
 {
 	eliminate_duplicate_dependencies(referenced);
-	recordMultipleDependencies(depender,
-							   referenced->refs, referenced->numrefs,
-							   behavior);
+
+	if (referenced->numrefs == 1)
+		recordDependencyOn(depender, &referenced->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   referenced->refs, referenced->numrefs,
+								   behavior);
 }
 
 /*
@@ -2632,6 +2646,18 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3b8c8b193a..5f443b798f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -678,6 +678,79 @@ CheckAttributeType(const char *attname,
 				 errhint("Use the COLLATE clause to set the collation explicitly.")));
 }
 
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
+ *
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
+ */
+void
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						FormData_pg_attribute *new_attributes,
+						int natts,
+						CatalogIndexState indstate)
+{
+	Datum		values[Natts_pg_attribute];
+	bool		nulls[Natts_pg_attribute];
+	HeapTuple	tup;
+	int			i;
+	TupleTableSlot **slot;
+
+	/*
+	 * The slots are dropped in CatalogMultiInsertWithInfo(). TODO: natts
+	 * number of slots is not a reasonable assumption as a wide relation
+	 * would be detrimental, figuring a good number is left as a TODO.
+	 */
+	slot = palloc(sizeof(TupleTableSlot *) * natts);
+
+	/* This is a tad tedious, but way cleaner than what we used to do... */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	/* start out with empty permissions and empty options */
+	nulls[Anum_pg_attribute_attacl - 1] = true;
+	nulls[Anum_pg_attribute_attoptions - 1] = true;
+	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
+	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+
+	/* attcacheoff is always -1 in storage */
+	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+
+	for (i = 0; i < natts; i++)
+	{
+		values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes[i].attrelid);
+		values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes[i].attname);
+		values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes[i].atttypid);
+		values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes[i].attstattarget);
+		values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes[i].attlen);
+		values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes[i].attnum);
+		values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes[i].attndims);
+		values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes[i].atttypmod);
+		values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes[i].attbyval);
+		values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes[i].attstorage);
+		values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes[i].attalign);
+		values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes[i].attnotnull);
+		values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes[i].atthasdef);
+		values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes[i].atthasmissing);
+		values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes[i].attidentity);
+		values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes[i].attgenerated);
+		values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes[i].attisdropped);
+		values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes[i].attislocal);
+		values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes[i].attinhcount);
+		values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes[i].attcollation);
+
+		slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_attribute_rel),
+										   &TTSOpsHeapTuple);
+		tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecStoreHeapTuple(heap_copytuple(tup), slot[i], false);
+	}
+
+	/* finally insert the new tuples, update the indexes, and clean up */
+	CatalogMultiInsertWithInfo(pg_attribute_rel, slot, natts, indstate);
+}
+
 /*
  * InsertPgAttributeTuple
  *		Construct and insert a new tuple in pg_attribute.
@@ -755,7 +828,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
+	Form_pg_attribute *attrs;
 	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
@@ -770,35 +843,42 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	attrs = palloc(sizeof(Form_pg_attribute) * natts);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * First we add the user attributes.
 	 */
 	for (i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
+		attrs[i] = TupleDescAttr(tupdesc, i);
 		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
+		attrs[i]->attrelid = new_rel_oid;
 		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
+		attrs[i]->attstattarget = -1;
+	}
 
-		InsertPgAttributeTuple(rel, attr, indstate);
+	InsertPgAttributeTuples(rel, *attrs, natts, indstate);
 
+	/*
+	 * Now add dependencies on their datatypes and collations.
+	 */
+	for (i = 0; i < natts; i++)
+	{
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = attrs[i]->atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(attrs[i]->attcollation) &&
+			attrs[i]->attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = attrs[i]->attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -811,22 +891,22 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
+		FormData_pg_attribute attStructs[lengthof(SysAtt)];
+
+		/* Fill in the correct relation OID in the copied tuple */
 		for (i = 0; i < (int) lengthof(SysAtt); i++)
 		{
-			FormData_pg_attribute attStruct;
-
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
-
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
+			memcpy(&attStructs[i], SysAtt[i], sizeof(FormData_pg_attribute));
+			attStructs[i].attrelid = new_rel_oid;
 		}
+
+		InsertPgAttributeTuples(rel, attStructs, lengthof(SysAtt), indstate);
 	}
 
 	/*
 	 * clean up
 	 */
+	pfree(attrs);
 	CatalogCloseIndexes(indstate);
 
 	table_close(rel, RowExclusiveLock);
@@ -3458,7 +3538,7 @@ StorePartitionKey(Relation rel,
 	Datum		values[Natts_pg_partitioned_table];
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
-	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3506,27 +3586,25 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
-
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_OPCLASS, partopclass[i], 0, refobjs);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
-
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_COLLATION, partcollation[i], 0, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * Anything mentioned in the expressions.  We must ignore the column
 	 * references, which will depend on the table itself; there is no separate
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bb60b23093..52d132113b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -505,14 +505,30 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
+	/*
+	 * If we only have a single attribute we can use the single tuple insert
+	 * function which avoids multi_insert scaffolding costs.
+	 */
+	if (numatts == 1)
 	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-
-		Assert(attr->attnum == i + 1);
+		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, 0);
 
 		InsertPgAttributeTuple(pg_attribute, attr, indstate);
 	}
+	else
+	{
+		Form_pg_attribute *attrs = palloc(sizeof(Form_pg_attribute) * numatts);
+
+		for (i = 0; i < numatts; i++)
+		{
+			attrs[i] = TupleDescAttr(indexTupDesc, i);
+
+			Assert(attrs[i]->attnum == i + 1);
+		}
+
+		InsertPgAttributeTuples(pg_attribute, *attrs, numatts, indstate);
+		pfree(attrs);
+	}
 
 	CatalogCloseIndexes(indstate);
 
@@ -982,11 +998,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1025,12 +1044,9 @@ index_create(Relation heapRelation,
 			{
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					add_object_address(OCLASS_CLASS, heapRelationId,
+									   indexInfo->ii_IndexAttrNumbers[i],
+									   refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1049,6 +1065,11 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+			{
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
+				reset_object_addresses(refobjs);
+			}
 		}
 
 		/*
@@ -1079,23 +1100,16 @@ index_create(Relation heapRelation,
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				add_object_address(OCLASS_COLLATION, collationObjectId[i], 0,
+								   refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
-		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			add_object_address(OCLASS_OPCLASS, classObjectId[i], 0, refobjs);
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
@@ -1116,6 +1130,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index f237e62bc9..0819a6449c 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,42 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, int ntuples,
+						   CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+		ExecDropSingleTupleTableSlot(slot[i]);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 7cab039ded..5d3d3e9069 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -104,8 +104,8 @@ AggregateCreate(const char *aggName,
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -740,83 +740,44 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_PROC, transfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, finalfn, 0, refobjs);
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, combinefn, 0, refobjs);
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, serialfn, 0, refobjs);
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, deserialfn, 0, refobjs);
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mtransfn, 0, refobjs);
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, minvtransfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mfinalfn, 0, refobjs);
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
-	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_OPERATOR, sortop, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 	return myself;
 }
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b6145593a3..be7c6b8c12 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -92,6 +92,7 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -230,30 +231,22 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
-			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
-			}
+				add_object_address(OCLASS_CLASS, relId, constraintKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			add_object_address(OCLASS_CLASS, relId, 0, refobjs);
 		}
 	}
 
@@ -262,39 +255,27 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
-
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
+		add_object_address(OCLASS_TYPE, domainId, 0, refobjs);
 	}
 
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
+	reset_object_addresses(refobjs);
+
 	if (OidIsValid(foreignRelId))
 	{
 		/*
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
-			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_CLASS, foreignRelId, foreignKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_CLASS, foreignRelId, 0, refobjs);
 		}
 	}
 
@@ -306,13 +287,7 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_CLASS, indexRelId, 0, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -323,28 +298,22 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_OPERATOR, pfEqOp[i], 0, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ppEqOp[i], 0, refobjs);
+
 			if (ffEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ffEqOp[i], 0, refobjs);
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 4116e93b64..8977169380 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,7 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	HeapTuple	tuple;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
@@ -81,7 +128,14 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 	memset(nulls, false, sizeof(nulls));
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
+
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -94,30 +148,34 @@ recordMultipleDependencies(const ObjectAddress *depender,
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
-
 			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
 			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
 			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
 
 			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+
+			tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreHeapTuple(heap_copytuple(tuple), slot[ntuples], false);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index bcaa26c997..b37f01df09 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,15 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -792,39 +794,19 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
-	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_SCHEMA, oper->oprnamespace, 0, refobjs);
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprleft, 0, refobjs);
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprright, 0, refobjs);
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprresult, 0, refobjs);
 
 	/*
 	 * NOTE: we do not consider the operator to depend on the associated
@@ -837,30 +819,18 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprcode, 0, refobjs);
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprrest, 0, refobjs);
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprjoin, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 02b7fdd494..a8195b3e21 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -114,8 +114,8 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	int			i;
 	Oid			trfid;
 
@@ -613,49 +613,29 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, procNamespace, 0, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_LANGUAGE, languageObjectId, 0, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_TYPE, returnType, 0, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
-	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_TYPE, allParams[i], 0, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
-		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+			add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 	}
 
 	/* dependency on parameter default expressions */
@@ -665,12 +645,10 @@ ProcedureCreate(const char *procedureName,
 
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, prosupport, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* dependency on owner */
 	if (!is_update)
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index fb7f8ddefc..5a24890d8a 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -66,7 +66,6 @@
 #include "utils/fmgroids.h"
 #include "utils/syscache.h"
 
-
 typedef enum
 {
 	LOCAL_OBJECT,
@@ -800,10 +799,15 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	HeapTuple	newtuple;
+	int			ntuples;
 	CatalogIndexState indstate;
 	Datum		values[Natts_pg_shdepend];
 	bool		nulls[Natts_pg_shdepend];
 	bool		replace[Natts_pg_shdepend];
+	/* TODO figure out a sensible value for the amount of slots */
+#define	DEPEND_TUPLE_BUF 32
+	TupleTableSlot *slot[DEPEND_TUPLE_BUF];
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
@@ -834,16 +838,26 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	ntuples = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+									&TTSOpsHeapTuple);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(heap_copytuple(newtuple), slot[ntuples], false);
+		ntuples++;
 
-		heap_freetuple(newtup);
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (ntuples)
+		CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 2a51501d8d..b3707b0f7e 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -544,7 +544,9 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool rebuild)
 {
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
@@ -579,63 +581,54 @@ GenerateTypeDependencies(Oid typeObjectId,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 151c3ef882..5468a6ecb8 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -974,7 +974,8 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r),
 								 buf->origptr, change);
 	}
-	Assert(data == tupledata + tuplelen);
+	Assert(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE &&
+		   data == tupledata + tuplelen);
 }
 
 /*
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index ef9c86864c..0a8d3ff613 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -162,6 +162,9 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
 
 extern ObjectAddresses *new_object_addresses(void);
 
+extern void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
+							   ObjectAddresses *addrs);
+
 extern void add_exact_object_address(const ObjectAddress *object,
 									 ObjectAddresses *addrs);
 
@@ -174,6 +177,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index eec71c29d5..24405977ff 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -95,6 +95,10 @@ extern List *heap_truncate_find_FKs(List *relationIds);
 extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
 								   Form_pg_attribute new_attribute,
 								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									FormData_pg_attribute *new_attributes,
+									int natts,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b017..4ea212ee59 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,8 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+						   int ntuples, CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
-- 
2.14.1.145.gb3622a4ee

#10Thomas Munro
thomas.munro@gmail.com
In reply to: Daniel Gustafsson (#9)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Tue, Jul 9, 2019 at 11:07 PM Daniel Gustafsson <daniel@yesql.se> wrote:

The attached v3 also has that fix in order to see if the cfbot is happier with
this.

Noticed while moving this to the next CF:

heap.c: In function ‘StorePartitionKey’:
1191heap.c:3582:3: error: ‘referenced’ undeclared (first use in this function)
1192 referenced.classId = RelationRelationId;
1193 ^

--
Thomas Munro
https://enterprisedb.com

#11Daniel Gustafsson
daniel@yesql.se
In reply to: Thomas Munro (#10)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 2 Aug 2019, at 00:41, Thomas Munro <thomas.munro@gmail.com> wrote:

On Tue, Jul 9, 2019 at 11:07 PM Daniel Gustafsson <daniel@yesql.se> wrote:

The attached v3 also has that fix in order to see if the cfbot is happier with
this.

Noticed while moving this to the next CF:

heap.c: In function ‘StorePartitionKey’:
1191heap.c:3582:3: error: ‘referenced’ undeclared (first use in this function)
1192 referenced.classId = RelationRelationId;
1193 ^

Thanks, the attached v4 updates to patch to handle a0555ddab9b672a046 as well.

cheers ./daniel

Attachments:

catalog_multi_insert-v4.patchapplication/octet-stream; name=catalog_multi_insert-v4.patch; x-unix-mode=0644Download
From 220b3640f1094899bc62b53be10aad9874b94bf4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 22 May 2019 09:59:55 +0200
Subject: [PATCH] WIP: Use heap_multi_insert for catalog relations

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/catalog/dependency.c         |  54 ++++++++----
 src/backend/catalog/heap.c               | 137 ++++++++++++++++++++++++-------
 src/backend/catalog/index.c              |  58 ++++++++-----
 src/backend/catalog/indexing.c           |  37 +++++++++
 src/backend/catalog/pg_aggregate.c       |  69 ++++------------
 src/backend/catalog/pg_constraint.c      |  75 +++++------------
 src/backend/catalog/pg_depend.c          |  84 ++++++++++++++++---
 src/backend/catalog/pg_operator.c        |  58 ++++---------
 src/backend/catalog/pg_proc.c            |  50 ++++-------
 src/backend/catalog/pg_shdepend.c        |  26 ++++--
 src/backend/catalog/pg_type.c            |  51 +++++-------
 src/backend/replication/logical/decode.c |   3 +-
 src/include/catalog/dependency.h         |   5 ++
 src/include/catalog/heap.h               |   4 +
 src/include/catalog/indexing.h           |   3 +
 15 files changed, 414 insertions(+), 300 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index dd0a7d8dac..5c6aa0d1dc 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -205,8 +205,6 @@ static bool find_expr_references_walker(Node *node,
 										find_expr_references_context *context);
 static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
 static int	object_address_comparator(const void *a, const void *b);
-static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
-							   ObjectAddresses *addrs);
 static void add_exact_object_address_extra(const ObjectAddress *object,
 										   const ObjectAddressExtra *extra,
 										   ObjectAddresses *addrs);
@@ -1600,10 +1598,16 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	/* Remove any duplicates */
 	eliminate_duplicate_dependencies(context.addrs);
 
-	/* And record 'em */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * And record 'em. If we know we only have a single dependency then
+	 * avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -1707,10 +1711,16 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		free_object_addresses(self_addrs);
 	}
 
-	/* Record the external dependencies */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * Record the external dependencies. If we only have a single dependency
+	 * then avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -2403,7 +2413,7 @@ new_object_addresses(void)
  * It is convenient to specify the class by ObjectClass rather than directly
  * by catalog OID.
  */
-static void
+void
 add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
 				   ObjectAddresses *addrs)
 {
@@ -2651,9 +2661,13 @@ record_object_address_dependencies(const ObjectAddress *depender,
 								   DependencyType behavior)
 {
 	eliminate_duplicate_dependencies(referenced);
-	recordMultipleDependencies(depender,
-							   referenced->refs, referenced->numrefs,
-							   behavior);
+
+	if (referenced->numrefs == 1)
+		recordDependencyOn(depender, &referenced->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   referenced->refs, referenced->numrefs,
+								   behavior);
 }
 
 /*
@@ -2673,6 +2687,18 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9d0f..098bf5a8da 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -677,6 +677,79 @@ CheckAttributeType(const char *attname,
 				 errhint("Use the COLLATE clause to set the collation explicitly.")));
 }
 
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
+ *
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
+ */
+void
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						FormData_pg_attribute *new_attributes,
+						int natts,
+						CatalogIndexState indstate)
+{
+	Datum		values[Natts_pg_attribute];
+	bool		nulls[Natts_pg_attribute];
+	HeapTuple	tup;
+	int			i;
+	TupleTableSlot **slot;
+
+	/*
+	 * The slots are dropped in CatalogMultiInsertWithInfo(). TODO: natts
+	 * number of slots is not a reasonable assumption as a wide relation
+	 * would be detrimental, figuring a good number is left as a TODO.
+	 */
+	slot = palloc(sizeof(TupleTableSlot *) * natts);
+
+	/* This is a tad tedious, but way cleaner than what we used to do... */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	/* start out with empty permissions and empty options */
+	nulls[Anum_pg_attribute_attacl - 1] = true;
+	nulls[Anum_pg_attribute_attoptions - 1] = true;
+	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
+	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+
+	/* attcacheoff is always -1 in storage */
+	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+
+	for (i = 0; i < natts; i++)
+	{
+		values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes[i].attrelid);
+		values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes[i].attname);
+		values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes[i].atttypid);
+		values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes[i].attstattarget);
+		values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes[i].attlen);
+		values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes[i].attnum);
+		values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes[i].attndims);
+		values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes[i].atttypmod);
+		values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes[i].attbyval);
+		values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes[i].attstorage);
+		values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes[i].attalign);
+		values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes[i].attnotnull);
+		values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes[i].atthasdef);
+		values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes[i].atthasmissing);
+		values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes[i].attidentity);
+		values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes[i].attgenerated);
+		values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes[i].attisdropped);
+		values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes[i].attislocal);
+		values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes[i].attinhcount);
+		values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes[i].attcollation);
+
+		slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_attribute_rel),
+										   &TTSOpsHeapTuple);
+		tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecStoreHeapTuple(heap_copytuple(tup), slot[i], false);
+	}
+
+	/* finally insert the new tuples, update the indexes, and clean up */
+	CatalogMultiInsertWithInfo(pg_attribute_rel, slot, natts, indstate);
+}
+
 /*
  * InsertPgAttributeTuple
  *		Construct and insert a new tuple in pg_attribute.
@@ -754,7 +827,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
+	Form_pg_attribute *attrs;
 	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
@@ -769,35 +842,42 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	attrs = palloc(sizeof(Form_pg_attribute) * natts);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * First we add the user attributes.
 	 */
 	for (i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
+		attrs[i] = TupleDescAttr(tupdesc, i);
 		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
+		attrs[i]->attrelid = new_rel_oid;
 		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
+		attrs[i]->attstattarget = -1;
+	}
 
-		InsertPgAttributeTuple(rel, attr, indstate);
+	InsertPgAttributeTuples(rel, *attrs, natts, indstate);
 
+	/*
+	 * Now add dependencies on their datatypes and collations.
+	 */
+	for (i = 0; i < natts; i++)
+	{
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = attrs[i]->atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(attrs[i]->attcollation) &&
+			attrs[i]->attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = attrs[i]->attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -810,22 +890,22 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
+		FormData_pg_attribute attStructs[lengthof(SysAtt)];
+
+		/* Fill in the correct relation OID in the copied tuple */
 		for (i = 0; i < (int) lengthof(SysAtt); i++)
 		{
-			FormData_pg_attribute attStruct;
-
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
-
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
+			memcpy(&attStructs[i], SysAtt[i], sizeof(FormData_pg_attribute));
+			attStructs[i].attrelid = new_rel_oid;
 		}
+
+		InsertPgAttributeTuples(rel, attStructs, lengthof(SysAtt), indstate);
 	}
 
 	/*
 	 * clean up
 	 */
+	pfree(attrs);
 	CatalogCloseIndexes(indstate);
 
 	table_close(rel, RowExclusiveLock);
@@ -3422,6 +3502,7 @@ StorePartitionKey(Relation rel,
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3469,27 +3550,25 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
-
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_OPCLASS, partopclass[i], 0, refobjs);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
-
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_COLLATION, partcollation[i], 0, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * The partitioning columns are made internally dependent on the table,
 	 * because we cannot drop any of them without dropping the whole table.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 3e1d40662d..6e9e020477 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -505,14 +505,30 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
+	/*
+	 * If we only have a single attribute we can use the single tuple insert
+	 * function which avoids multi_insert scaffolding costs.
+	 */
+	if (numatts == 1)
 	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-
-		Assert(attr->attnum == i + 1);
+		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, 0);
 
 		InsertPgAttributeTuple(pg_attribute, attr, indstate);
 	}
+	else
+	{
+		Form_pg_attribute *attrs = palloc(sizeof(Form_pg_attribute) * numatts);
+
+		for (i = 0; i < numatts; i++)
+		{
+			attrs[i] = TupleDescAttr(indexTupDesc, i);
+
+			Assert(attrs[i]->attnum == i + 1);
+		}
+
+		InsertPgAttributeTuples(pg_attribute, *attrs, numatts, indstate);
+		pfree(attrs);
+	}
 
 	CatalogCloseIndexes(indstate);
 
@@ -982,11 +998,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1025,12 +1044,9 @@ index_create(Relation heapRelation,
 			{
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					add_object_address(OCLASS_CLASS, heapRelationId,
+									   indexInfo->ii_IndexAttrNumbers[i],
+									   refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1049,6 +1065,11 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+			{
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
+				reset_object_addresses(refobjs);
+			}
 		}
 
 		/*
@@ -1079,23 +1100,16 @@ index_create(Relation heapRelation,
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				add_object_address(OCLASS_COLLATION, collationObjectId[i], 0,
+								   refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
-		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			add_object_address(OCLASS_OPCLASS, classObjectId[i], 0, refobjs);
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
@@ -1116,6 +1130,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index f237e62bc9..0819a6449c 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,42 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, int ntuples,
+						   CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+		ExecDropSingleTupleTableSlot(slot[i]);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 201242e796..78550d5d29 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -104,8 +104,8 @@ AggregateCreate(const char *aggName,
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -740,83 +740,44 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_PROC, transfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, finalfn, 0, refobjs);
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, combinefn, 0, refobjs);
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, serialfn, 0, refobjs);
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, deserialfn, 0, refobjs);
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mtransfn, 0, refobjs);
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, minvtransfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mfinalfn, 0, refobjs);
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
-	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_OPERATOR, sortop, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 	return myself;
 }
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b6145593a3..be7c6b8c12 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -92,6 +92,7 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -230,30 +231,22 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
-			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
-			}
+				add_object_address(OCLASS_CLASS, relId, constraintKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			add_object_address(OCLASS_CLASS, relId, 0, refobjs);
 		}
 	}
 
@@ -262,39 +255,27 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
-
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
+		add_object_address(OCLASS_TYPE, domainId, 0, refobjs);
 	}
 
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
+	reset_object_addresses(refobjs);
+
 	if (OidIsValid(foreignRelId))
 	{
 		/*
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
-			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_CLASS, foreignRelId, foreignKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_CLASS, foreignRelId, 0, refobjs);
 		}
 	}
 
@@ -306,13 +287,7 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_CLASS, indexRelId, 0, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -323,28 +298,22 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_OPERATOR, pfEqOp[i], 0, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ppEqOp[i], 0, refobjs);
+
 			if (ffEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ffEqOp[i], 0, refobjs);
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index a060c25d2e..b20004642a 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,7 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	HeapTuple	tuple;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
@@ -81,7 +128,14 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 	memset(nulls, false, sizeof(nulls));
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
+
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -94,30 +148,34 @@ recordMultipleDependencies(const ObjectAddress *depender,
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
-
 			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
 			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
 			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
 
 			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+
+			tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreHeapTuple(heap_copytuple(tuple), slot[ntuples], false);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index bcaa26c997..b37f01df09 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,15 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -792,39 +794,19 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
-	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_SCHEMA, oper->oprnamespace, 0, refobjs);
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprleft, 0, refobjs);
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprright, 0, refobjs);
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprresult, 0, refobjs);
 
 	/*
 	 * NOTE: we do not consider the operator to depend on the associated
@@ -837,30 +819,18 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprcode, 0, refobjs);
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprrest, 0, refobjs);
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprjoin, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index ef009ad2bc..e81c78f7d3 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -114,8 +114,8 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	int			i;
 	Oid			trfid;
 
@@ -611,49 +611,29 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, procNamespace, 0, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_LANGUAGE, languageObjectId, 0, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_TYPE, returnType, 0, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
-	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_TYPE, allParams[i], 0, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
-		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+			add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 	}
 
 	/* dependency on parameter default expressions */
@@ -663,12 +643,10 @@ ProcedureCreate(const char *procedureName,
 
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, prosupport, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* dependency on owner */
 	if (!is_update)
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index fb7f8ddefc..5a24890d8a 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -66,7 +66,6 @@
 #include "utils/fmgroids.h"
 #include "utils/syscache.h"
 
-
 typedef enum
 {
 	LOCAL_OBJECT,
@@ -800,10 +799,15 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	HeapTuple	newtuple;
+	int			ntuples;
 	CatalogIndexState indstate;
 	Datum		values[Natts_pg_shdepend];
 	bool		nulls[Natts_pg_shdepend];
 	bool		replace[Natts_pg_shdepend];
+	/* TODO figure out a sensible value for the amount of slots */
+#define	DEPEND_TUPLE_BUF 32
+	TupleTableSlot *slot[DEPEND_TUPLE_BUF];
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
@@ -834,16 +838,26 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	ntuples = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+									&TTSOpsHeapTuple);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(heap_copytuple(newtuple), slot[ntuples], false);
+		ntuples++;
 
-		heap_freetuple(newtup);
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (ntuples)
+		CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 2a51501d8d..b3707b0f7e 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -544,7 +544,9 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool rebuild)
 {
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
@@ -579,63 +581,54 @@ GenerateTypeDependencies(Oid typeObjectId,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 151c3ef882..5468a6ecb8 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -974,7 +974,8 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r),
 								 buf->origptr, change);
 	}
-	Assert(data == tupledata + tuplelen);
+	Assert(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE &&
+		   data == tupledata + tuplelen);
 }
 
 /*
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index ff50d594f6..7e3bcde2ea 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -162,6 +162,9 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
 
 extern ObjectAddresses *new_object_addresses(void);
 
+extern void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
+							   ObjectAddresses *addrs);
+
 extern void add_exact_object_address(const ObjectAddress *object,
 									 ObjectAddresses *addrs);
 
@@ -174,6 +177,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index eec71c29d5..24405977ff 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -95,6 +95,10 @@ extern List *heap_truncate_find_FKs(List *relationIds);
 extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
 								   Form_pg_attribute new_attribute,
 								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									FormData_pg_attribute *new_attributes,
+									int natts,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b017..4ea212ee59 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,8 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+						   int ntuples, CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
-- 
2.14.1.145.gb3622a4ee

#12Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#11)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Mon, Aug 05, 2019 at 09:20:07AM +0200, Daniel Gustafsson wrote:

Thanks, the attached v4 updates to patch to handle a0555ddab9b672a046 as well.

+   if (referenced->numrefs == 1)
+       recordDependencyOn(depender, &referenced->refs[0], behavior);
+   else
+       recordMultipleDependencies(depender,
+                                  referenced->refs, referenced->numrefs,
+                                  behavior);
This makes me wonder if we should not just add a shortcut in
recordMultipleDependencies() to use recordDependencyOn if there is
only one reference in the set.  That would save the effort of a multi
insert for all callers of recordMultipleDependencies() this way,
including the future ones.  And that could also be done independently
of the addition of InsertPgAttributeTuples(), no?
--
Michael
#13Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#12)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Mon, Aug 05, 2019 at 04:45:59PM +0900, Michael Paquier wrote:

+   if (referenced->numrefs == 1)
+       recordDependencyOn(depender, &referenced->refs[0], behavior);
+   else
+       recordMultipleDependencies(depender,
+                                  referenced->refs, referenced->numrefs,
+                                  behavior);
This makes me wonder if we should not just add a shortcut in
recordMultipleDependencies() to use recordDependencyOn if there is
only one reference in the set.  That would save the effort of a multi
insert for all callers of recordMultipleDependencies() this way,
including the future ones.  And that could also be done independently
of the addition of InsertPgAttributeTuples(), no?

A comment in heap_multi_insert() needs to be updated because it
becomes the case with your patch:
/*
* We don't use heap_multi_insert for catalog tuples yet, but
* better be prepared...
*/

There is also this one in DecodeMultiInsert()
* CONTAINS_NEW_TUPLE will always be set currently as multi_insert
* isn't used for catalogs, but better be future proof.

(I am going to comment on the assertion issue on the other thread, I
got some suggestions about it.)
--
Michael

#14Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#13)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Tue, Aug 06, 2019 at 11:24:46AM +0900, Michael Paquier wrote:

A comment in heap_multi_insert() needs to be updated because it
becomes the case with your patch:
/*
* We don't use heap_multi_insert for catalog tuples yet, but
* better be prepared...
*/

There is also this one in DecodeMultiInsert()
* CONTAINS_NEW_TUPLE will always be set currently as multi_insert
* isn't used for catalogs, but better be future proof.

Applying the latest patch, this results in an assertion failure for
the tests of test_decoding.

(I am going to comment on the assertion issue on the other thread, I
got some suggestions about it.)

This part has resulted in 75c1921, and we could just change
DecodeMultiInsert() so as if there is no tuple data then we'd just
leave. However, I don't feel completely comfortable with that either
as it would be nice to still check that for normal relations we
*always* have a FPW available.

Daniel, your thoughts? I am switching the patch as waiting on
author.
--
Michael

#15Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#14)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 11 Nov 2019, at 09:32, Michael Paquier <michael@paquier.xyz> wrote:

This part has resulted in 75c1921, and we could just change
DecodeMultiInsert() so as if there is no tuple data then we'd just
leave. However, I don't feel completely comfortable with that either
as it would be nice to still check that for normal relations we
*always* have a FPW available.

XLH_INSERT_CONTAINS_NEW_TUPLE will only be set in case of catalog relations
IIUC (that is, non logically decoded relations), so it seems to me that we can
have a fastpath out of DecodeMultiInsert() which inspects that flag without
problems. Is this proposal along the lines of what you were thinking?

The patch is now generating an error in the regression tests as well, on your
recently added CREATE INDEX test from 68ac9cf2499236996f3d4bf31f7f16d5bd3c77af.
By using the ObjectAddresses API the dependencies are deduped before recorded,
removing the duplicate entry from that test output. AFAICT there is nothing
benefiting us from having duplicate dependencies, so this seems an improvement
(albeit tiny and not very important), or am I missing something? Is there a
use for duplicate dependencies?

Attached version contains the above two fixes, as well as a multi_insert for
dependencies in CREATE EXTENSION which I had missed to git add in previous
versions.

cheers ./daniel

Attachments:

catalog_multi_insert-v5.patchapplication/octet-stream; name=catalog_multi_insert-v5.patch; x-unix-mode=0644Download
From e53ca5292cd26e3bdc1c0f943a678e95938ef0ce Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Tue, 12 Nov 2019 15:14:10 +0100
Subject: [PATCH] WIP: Use heap_multi_insert for catalog relations, v5

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/catalog/dependency.c           |  54 +++++---
 src/backend/catalog/heap.c                 | 137 ++++++++++++++++-----
 src/backend/catalog/index.c                |  58 +++++----
 src/backend/catalog/indexing.c             |  37 ++++++
 src/backend/catalog/pg_aggregate.c         |  69 +++--------
 src/backend/catalog/pg_constraint.c        |  75 ++++-------
 src/backend/catalog/pg_depend.c            |  84 +++++++++++--
 src/backend/catalog/pg_operator.c          |  58 +++------
 src/backend/catalog/pg_proc.c              |  50 +++-----
 src/backend/catalog/pg_shdepend.c          |  26 +++-
 src/backend/catalog/pg_type.c              |  51 ++++----
 src/backend/commands/extension.c           |  21 ++--
 src/backend/replication/logical/decode.c   |  62 +++++-----
 src/include/catalog/dependency.h           |   5 +
 src/include/catalog/heap.h                 |   4 +
 src/include/catalog/indexing.h             |   3 +
 src/test/regress/expected/create_index.out |   6 +-
 17 files changed, 453 insertions(+), 347 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 03582781f6..8e59f65bd6 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -205,8 +205,6 @@ static bool find_expr_references_walker(Node *node,
 										find_expr_references_context *context);
 static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
 static int	object_address_comparator(const void *a, const void *b);
-static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
-							   ObjectAddresses *addrs);
 static void add_exact_object_address_extra(const ObjectAddress *object,
 										   const ObjectAddressExtra *extra,
 										   ObjectAddresses *addrs);
@@ -1600,10 +1598,16 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	/* Remove any duplicates */
 	eliminate_duplicate_dependencies(context.addrs);
 
-	/* And record 'em */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * And record 'em. If we know we only have a single dependency then
+	 * avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -1707,10 +1711,16 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		free_object_addresses(self_addrs);
 	}
 
-	/* Record the external dependencies */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * Record the external dependencies. If we only have a single dependency
+	 * then avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -2398,7 +2408,7 @@ new_object_addresses(void)
  * It is convenient to specify the class by ObjectClass rather than directly
  * by catalog OID.
  */
-static void
+void
 add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
 				   ObjectAddresses *addrs)
 {
@@ -2646,9 +2656,13 @@ record_object_address_dependencies(const ObjectAddress *depender,
 								   DependencyType behavior)
 {
 	eliminate_duplicate_dependencies(referenced);
-	recordMultipleDependencies(depender,
-							   referenced->refs, referenced->numrefs,
-							   behavior);
+
+	if (referenced->numrefs == 1)
+		recordDependencyOn(depender, &referenced->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   referenced->refs, referenced->numrefs,
+								   behavior);
 }
 
 /*
@@ -2668,6 +2682,18 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9d0f..098bf5a8da 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -677,6 +677,79 @@ CheckAttributeType(const char *attname,
 				 errhint("Use the COLLATE clause to set the collation explicitly.")));
 }
 
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
+ *
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
+ */
+void
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						FormData_pg_attribute *new_attributes,
+						int natts,
+						CatalogIndexState indstate)
+{
+	Datum		values[Natts_pg_attribute];
+	bool		nulls[Natts_pg_attribute];
+	HeapTuple	tup;
+	int			i;
+	TupleTableSlot **slot;
+
+	/*
+	 * The slots are dropped in CatalogMultiInsertWithInfo(). TODO: natts
+	 * number of slots is not a reasonable assumption as a wide relation
+	 * would be detrimental, figuring a good number is left as a TODO.
+	 */
+	slot = palloc(sizeof(TupleTableSlot *) * natts);
+
+	/* This is a tad tedious, but way cleaner than what we used to do... */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	/* start out with empty permissions and empty options */
+	nulls[Anum_pg_attribute_attacl - 1] = true;
+	nulls[Anum_pg_attribute_attoptions - 1] = true;
+	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
+	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+
+	/* attcacheoff is always -1 in storage */
+	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+
+	for (i = 0; i < natts; i++)
+	{
+		values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes[i].attrelid);
+		values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes[i].attname);
+		values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes[i].atttypid);
+		values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes[i].attstattarget);
+		values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes[i].attlen);
+		values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes[i].attnum);
+		values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes[i].attndims);
+		values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes[i].atttypmod);
+		values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes[i].attbyval);
+		values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes[i].attstorage);
+		values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes[i].attalign);
+		values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes[i].attnotnull);
+		values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes[i].atthasdef);
+		values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes[i].atthasmissing);
+		values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes[i].attidentity);
+		values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes[i].attgenerated);
+		values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes[i].attisdropped);
+		values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes[i].attislocal);
+		values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes[i].attinhcount);
+		values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes[i].attcollation);
+
+		slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_attribute_rel),
+										   &TTSOpsHeapTuple);
+		tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecStoreHeapTuple(heap_copytuple(tup), slot[i], false);
+	}
+
+	/* finally insert the new tuples, update the indexes, and clean up */
+	CatalogMultiInsertWithInfo(pg_attribute_rel, slot, natts, indstate);
+}
+
 /*
  * InsertPgAttributeTuple
  *		Construct and insert a new tuple in pg_attribute.
@@ -754,7 +827,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
+	Form_pg_attribute *attrs;
 	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
@@ -769,35 +842,42 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	attrs = palloc(sizeof(Form_pg_attribute) * natts);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * First we add the user attributes.
 	 */
 	for (i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
+		attrs[i] = TupleDescAttr(tupdesc, i);
 		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
+		attrs[i]->attrelid = new_rel_oid;
 		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
+		attrs[i]->attstattarget = -1;
+	}
 
-		InsertPgAttributeTuple(rel, attr, indstate);
+	InsertPgAttributeTuples(rel, *attrs, natts, indstate);
 
+	/*
+	 * Now add dependencies on their datatypes and collations.
+	 */
+	for (i = 0; i < natts; i++)
+	{
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = attrs[i]->atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(attrs[i]->attcollation) &&
+			attrs[i]->attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = attrs[i]->attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -810,22 +890,22 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
+		FormData_pg_attribute attStructs[lengthof(SysAtt)];
+
+		/* Fill in the correct relation OID in the copied tuple */
 		for (i = 0; i < (int) lengthof(SysAtt); i++)
 		{
-			FormData_pg_attribute attStruct;
-
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
-
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
+			memcpy(&attStructs[i], SysAtt[i], sizeof(FormData_pg_attribute));
+			attStructs[i].attrelid = new_rel_oid;
 		}
+
+		InsertPgAttributeTuples(rel, attStructs, lengthof(SysAtt), indstate);
 	}
 
 	/*
 	 * clean up
 	 */
+	pfree(attrs);
 	CatalogCloseIndexes(indstate);
 
 	table_close(rel, RowExclusiveLock);
@@ -3422,6 +3502,7 @@ StorePartitionKey(Relation rel,
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3469,27 +3550,25 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
-
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_OPCLASS, partopclass[i], 0, refobjs);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
-
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_COLLATION, partcollation[i], 0, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * The partitioning columns are made internally dependent on the table,
 	 * because we cannot drop any of them without dropping the whole table.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7c34509696..f8260879f0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -505,14 +505,30 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
+	/*
+	 * If we only have a single attribute we can use the single tuple insert
+	 * function which avoids multi_insert scaffolding costs.
+	 */
+	if (numatts == 1)
 	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-
-		Assert(attr->attnum == i + 1);
+		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, 0);
 
 		InsertPgAttributeTuple(pg_attribute, attr, indstate);
 	}
+	else
+	{
+		Form_pg_attribute *attrs = palloc(sizeof(Form_pg_attribute) * numatts);
+
+		for (i = 0; i < numatts; i++)
+		{
+			attrs[i] = TupleDescAttr(indexTupDesc, i);
+
+			Assert(attrs[i]->attnum == i + 1);
+		}
+
+		InsertPgAttributeTuples(pg_attribute, *attrs, numatts, indstate);
+		pfree(attrs);
+	}
 
 	CatalogCloseIndexes(indstate);
 
@@ -1027,11 +1043,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1070,12 +1089,9 @@ index_create(Relation heapRelation,
 			{
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					add_object_address(OCLASS_CLASS, heapRelationId,
+									   indexInfo->ii_IndexAttrNumbers[i],
+									   refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1094,6 +1110,11 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+			{
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
+				reset_object_addresses(refobjs);
+			}
 		}
 
 		/*
@@ -1124,23 +1145,16 @@ index_create(Relation heapRelation,
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				add_object_address(OCLASS_COLLATION, collationObjectId[i], 0,
+								   refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
-		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			add_object_address(OCLASS_OPCLASS, classObjectId[i], 0, refobjs);
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
@@ -1161,6 +1175,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index f237e62bc9..0819a6449c 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,42 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, int ntuples,
+						   CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+		ExecDropSingleTupleTableSlot(slot[i]);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 1ac235a0f4..a4b31cc30b 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -104,8 +104,8 @@ AggregateCreate(const char *aggName,
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -740,83 +740,44 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_PROC, transfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, finalfn, 0, refobjs);
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, combinefn, 0, refobjs);
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, serialfn, 0, refobjs);
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, deserialfn, 0, refobjs);
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mtransfn, 0, refobjs);
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, minvtransfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mfinalfn, 0, refobjs);
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
-	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_OPERATOR, sortop, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 	return myself;
 }
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 56568b0105..68f1313ec4 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -92,6 +92,7 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -230,30 +231,22 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
-			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
-			}
+				add_object_address(OCLASS_CLASS, relId, constraintKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			add_object_address(OCLASS_CLASS, relId, 0, refobjs);
 		}
 	}
 
@@ -262,39 +255,27 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
-
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
+		add_object_address(OCLASS_TYPE, domainId, 0, refobjs);
 	}
 
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
+	reset_object_addresses(refobjs);
+
 	if (OidIsValid(foreignRelId))
 	{
 		/*
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
-			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_CLASS, foreignRelId, foreignKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_CLASS, foreignRelId, 0, refobjs);
 		}
 	}
 
@@ -306,13 +287,7 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_CLASS, indexRelId, 0, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -323,28 +298,22 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_OPERATOR, pfEqOp[i], 0, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ppEqOp[i], 0, refobjs);
+
 			if (ffEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ffEqOp[i], 0, refobjs);
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index a060c25d2e..b20004642a 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,7 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	HeapTuple	tuple;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
@@ -81,7 +128,14 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 	memset(nulls, false, sizeof(nulls));
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
+
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -94,30 +148,34 @@ recordMultipleDependencies(const ObjectAddress *depender,
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
-
 			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
 			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
 			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
 
 			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+
+			tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreHeapTuple(heap_copytuple(tuple), slot[ntuples], false);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index bcaa26c997..b37f01df09 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,15 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -792,39 +794,19 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
-	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_SCHEMA, oper->oprnamespace, 0, refobjs);
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprleft, 0, refobjs);
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprright, 0, refobjs);
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprresult, 0, refobjs);
 
 	/*
 	 * NOTE: we do not consider the operator to depend on the associated
@@ -837,30 +819,18 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprcode, 0, refobjs);
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprrest, 0, refobjs);
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprjoin, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index ef009ad2bc..e81c78f7d3 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -114,8 +114,8 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	int			i;
 	Oid			trfid;
 
@@ -611,49 +611,29 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, procNamespace, 0, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_LANGUAGE, languageObjectId, 0, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_TYPE, returnType, 0, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
-	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_TYPE, allParams[i], 0, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
-		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+			add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 	}
 
 	/* dependency on parameter default expressions */
@@ -663,12 +643,10 @@ ProcedureCreate(const char *procedureName,
 
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, prosupport, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* dependency on owner */
 	if (!is_update)
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index fb7f8ddefc..5a24890d8a 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -66,7 +66,6 @@
 #include "utils/fmgroids.h"
 #include "utils/syscache.h"
 
-
 typedef enum
 {
 	LOCAL_OBJECT,
@@ -800,10 +799,15 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	HeapTuple	newtuple;
+	int			ntuples;
 	CatalogIndexState indstate;
 	Datum		values[Natts_pg_shdepend];
 	bool		nulls[Natts_pg_shdepend];
 	bool		replace[Natts_pg_shdepend];
+	/* TODO figure out a sensible value for the amount of slots */
+#define	DEPEND_TUPLE_BUF 32
+	TupleTableSlot *slot[DEPEND_TUPLE_BUF];
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
@@ -834,16 +838,26 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	ntuples = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+									&TTSOpsHeapTuple);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(heap_copytuple(newtuple), slot[ntuples], false);
+		ntuples++;
 
-		heap_freetuple(newtup);
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (ntuples)
+		CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 2a51501d8d..b3707b0f7e 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -544,7 +544,9 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool rebuild)
 {
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
@@ -579,63 +581,54 @@ GenerateTypeDependencies(Oid typeObjectId,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index a04b0c9e57..5bac394c3b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1771,7 +1771,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	bool		nulls[Natts_pg_extension];
 	HeapTuple	tuple;
 	ObjectAddress myself;
-	ObjectAddress nsp;
+	ObjectAddresses *refobjs;
 	ListCell   *lc;
 
 	/*
@@ -1812,29 +1812,26 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	/*
 	 * Record dependencies on owner, schema, and prerequisite extensions
 	 */
+	refobjs = new_object_addresses();
+
 	recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
 
 	myself.classId = ExtensionRelationId;
 	myself.objectId = extensionOid;
 	myself.objectSubId = 0;
 
-	nsp.classId = NamespaceRelationId;
-	nsp.objectId = schemaOid;
-	nsp.objectSubId = 0;
-
-	recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, schemaOid, 0, refobjs);
 
 	foreach(lc, requiredExtensions)
 	{
 		Oid			reqext = lfirst_oid(lc);
-		ObjectAddress otherext;
-
-		otherext.classId = ExtensionRelationId;
-		otherext.objectId = reqext;
-		otherext.objectSubId = 0;
 
-		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_EXTENSION, reqext, 0, refobjs);
 	}
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index c53e7e2279..f28e65b840 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -894,6 +894,14 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
 
+	/*
+	 * CONTAINS_NEW_TUPLE will always be set unless the multi_insert was
+	 * performed for a catalog.  If it is a catalog, return immediately as
+	 * there is nothing to logically decode.
+	 */
+	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
+		return;
+
 	/* only interested in our database */
 	XLogRecGetBlockTag(r, 0, &rnode, NULL, NULL);
 	if (rnode.dbNode != ctx->slot->data.database)
@@ -917,6 +925,7 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		xl_multi_insert_tuple *xlhdr;
 		int			datalen;
 		ReorderBufferTupleBuf *tuple;
+		HeapTupleHeader header;
 
 		change = ReorderBufferGetChange(ctx->reorder);
 		change->action = REORDER_BUFFER_CHANGE_INSERT;
@@ -928,43 +937,31 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		data = ((char *) xlhdr) + SizeOfMultiInsertTuple;
 		datalen = xlhdr->datalen;
 
-		/*
-		 * CONTAINS_NEW_TUPLE will always be set currently as multi_insert
-		 * isn't used for catalogs, but better be future proof.
-		 *
-		 * We decode the tuple in pretty much the same way as DecodeXLogTuple,
-		 * but since the layout is slightly different, we can't use it here.
-		 */
-		if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)
-		{
-			HeapTupleHeader header;
-
-			change->data.tp.newtuple =
-				ReorderBufferGetTupleBuf(ctx->reorder, datalen);
+		change->data.tp.newtuple =
+			ReorderBufferGetTupleBuf(ctx->reorder, datalen);
 
-			tuple = change->data.tp.newtuple;
-			header = tuple->tuple.t_data;
+		tuple = change->data.tp.newtuple;
+		header = tuple->tuple.t_data;
 
-			/* not a disk based tuple */
-			ItemPointerSetInvalid(&tuple->tuple.t_self);
+		/* not a disk based tuple */
+		ItemPointerSetInvalid(&tuple->tuple.t_self);
 
-			/*
-			 * We can only figure this out after reassembling the
-			 * transactions.
-			 */
-			tuple->tuple.t_tableOid = InvalidOid;
+		/*
+		 * We can only figure this out after reassembling the
+		 * transactions.
+		 */
+		tuple->tuple.t_tableOid = InvalidOid;
 
-			tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
+		tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
 
-			memset(header, 0, SizeofHeapTupleHeader);
+		memset(header, 0, SizeofHeapTupleHeader);
 
-			memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
-				   (char *) data,
-				   datalen);
-			header->t_infomask = xlhdr->t_infomask;
-			header->t_infomask2 = xlhdr->t_infomask2;
-			header->t_hoff = xlhdr->t_hoff;
-		}
+		memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
+			   (char *) data,
+			   datalen);
+		header->t_infomask = xlhdr->t_infomask;
+		header->t_infomask2 = xlhdr->t_infomask2;
+		header->t_hoff = xlhdr->t_hoff;
 
 		/*
 		 * Reset toast reassembly state only after the last row in the last
@@ -983,7 +980,8 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		/* move to the next xl_multi_insert_tuple entry */
 		data += datalen;
 	}
-	Assert(data == tupledata + tuplelen);
+	Assert(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE &&
+		   data == tupledata + tuplelen);
 }
 
 /*
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index ff50d594f6..7e3bcde2ea 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -162,6 +162,9 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
 
 extern ObjectAddresses *new_object_addresses(void);
 
+extern void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
+							   ObjectAddresses *addrs);
+
 extern void add_exact_object_address(const ObjectAddress *object,
 									 ObjectAddresses *addrs);
 
@@ -174,6 +177,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index eec71c29d5..24405977ff 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -95,6 +95,10 @@ extern List *heap_truncate_find_FKs(List *relationIds);
 extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
 								   Form_pg_attribute new_attribute,
 								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									FormData_pg_attribute *new_attributes,
+									int natts,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b017..4ea212ee59 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,8 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+						   int ntuples, CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 1cdb7a9663..8a4b9cb7cd 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1994,11 +1994,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2022,11 +2021,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
-- 
2.21.0 (Apple Git-122.2)

#16Andres Freund
andres@anarazel.de
In reply to: Daniel Gustafsson (#15)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 2019-11-12 16:25:06 +0100, Daniel Gustafsson wrote:

On 11 Nov 2019, at 09:32, Michael Paquier <michael@paquier.xyz> wrote:

This part has resulted in 75c1921, and we could just change
DecodeMultiInsert() so as if there is no tuple data then we'd just
leave. However, I don't feel completely comfortable with that either
as it would be nice to still check that for normal relations we
*always* have a FPW available.

XLH_INSERT_CONTAINS_NEW_TUPLE will only be set in case of catalog relations
IIUC (that is, non logically decoded relations), so it seems to me that we can
have a fastpath out of DecodeMultiInsert() which inspects that flag without
problems. Is this proposal along the lines of what you were thinking?

Maybe I'm missing something, but it's the opposite, no?

bool need_tuple_data = RelationIsLogicallyLogged(relation);

...
if (need_tuple_data)
xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;

or am I misunderstanding what you mean?

@@ -1600,10 +1598,16 @@ recordDependencyOnExpr(const ObjectAddress *depender,
/* Remove any duplicates */
eliminate_duplicate_dependencies(context.addrs);

-	/* And record 'em */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * And record 'em. If we know we only have a single dependency then
+	 * avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);

I'm not sure this is actually a worthwhile complexity to keep. Hard to
believe that setting up a multi-insert is goign to have a significant
enough overhead to matter here?

And if it does, is there a chance we can hide this repeated block
somewhere within recordMultipleDependencies() or such? I don't like the
repetitiveness here. Seems recordMultipleDependencies() could easily
optimize the case of there being exactly one dependency to insert?

+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
+ *
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
+ */
+void
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						FormData_pg_attribute *new_attributes,
+						int natts,
+						CatalogIndexState indstate)
+{
+	Datum		values[Natts_pg_attribute];
+	bool		nulls[Natts_pg_attribute];
+	HeapTuple	tup;
+	int			i;
+	TupleTableSlot **slot;
+
+	/*
+	 * The slots are dropped in CatalogMultiInsertWithInfo(). TODO: natts
+	 * number of slots is not a reasonable assumption as a wide relation
+	 * would be detrimental, figuring a good number is left as a TODO.
+	 */
+	slot = palloc(sizeof(TupleTableSlot *) * natts);

Hm. Looking at

SELECT avg(pg_column_size(pa)) FROM pg_attribute pa;

yielding ~144 bytes, we can probably cap this at 128 or such, without
loosing efficiency. Or just use
#define MAX_BUFFERED_BYTES 65535
from copy.c or such (MAX_BUFFERED_BYTES / sizeof(FormData_pg_attribute)).

+ /* This is a tad tedious, but way cleaner than what we used to do... */

I don't like comments that refer to "what we used to" because there's no
way for anybody to make sense of that, because it's basically a dangling
reference :)

+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));

+	/* start out with empty permissions and empty options */
+	nulls[Anum_pg_attribute_attacl - 1] = true;
+	nulls[Anum_pg_attribute_attoptions - 1] = true;
+	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
+	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+
+	/* attcacheoff is always -1 in storage */
+	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+
+	for (i = 0; i < natts; i++)
+	{
+		values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes[i].attrelid);
+		values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes[i].attname);
+		values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes[i].atttypid);
+		values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes[i].attstattarget);
+		values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes[i].attlen);
+		values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes[i].attnum);
+		values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes[i].attndims);
+		values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes[i].atttypmod);
+		values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes[i].attbyval);
+		values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes[i].attstorage);
+		values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes[i].attalign);
+		values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes[i].attnotnull);
+		values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes[i].atthasdef);
+		values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes[i].atthasmissing);
+		values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes[i].attidentity);
+		values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes[i].attgenerated);
+		values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes[i].attisdropped);
+		values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes[i].attislocal);
+		values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes[i].attinhcount);
+		values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes[i].attcollation);
+
+		slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_attribute_rel),
+										   &TTSOpsHeapTuple);
+		tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecStoreHeapTuple(heap_copytuple(tup), slot[i], false);

This seems likely to waste some effort - during insertion the slot will
be materialized, which'll copy the tuple. I think it'd be better to
construct the tuple directly inside the slot's tts_values/isnull, and
then store it with ExecStoreVirtualTuple().

Also, why aren't you actually just specifying shouldFree = true? We'd
want the result of the heap_form_tuple() to be freed eagerly, no? Nor do
I get why you're *also* heap_copytuple'ing the tuple, despite having
just locally formed it, and not referencing the result of
heap_form_tuple() further? Obviously that's all unimportant if you
change the code to use ExecStoreVirtualTuple()?

+	}
+
+	/* finally insert the new tuples, update the indexes, and clean up */
+	CatalogMultiInsertWithInfo(pg_attribute_rel, slot, natts, indstate);

Previous comment:

I think it might be worthwhile to clear and delete the slots
after inserting. That's potentially a good bit of memory, no?

Current comment:

I think I quite dislike the API of CatalogMultiInsertWithInfo freeing
the slots. It'd e.g. make it impossible to reuse them to insert more
data. It's also really hard to understand

+}
+
/*
* InsertPgAttributeTuple
* Construct and insert a new tuple in pg_attribute.
@@ -754,7 +827,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
TupleDesc tupdesc,
char relkind)
{
- Form_pg_attribute attr;
+ Form_pg_attribute *attrs;
int i;
Relation rel;
CatalogIndexState indstate;
@@ -769,35 +842,42 @@ AddNewAttributeTuples(Oid new_rel_oid,

indstate = CatalogOpenIndexes(rel);

+ attrs = palloc(sizeof(Form_pg_attribute) * natts);

Hm. Why we we need this separate allocation? Isn't this exactly the same
as what's in the tupledesc?

+/*
+ * CatalogMultiInsertWithInfo

Hm. The current function is called CatalogTupleInsert(), wouldn't
CatalogTuplesInsertWithInfo() or such be more accurate? Or
CatalogTuplesMultiInsertWithInfo()?

@@ -81,7 +128,14 @@ recordMultipleDependencies(const ObjectAddress *depender,

memset(nulls, false, sizeof(nulls));

-	for (i = 0; i < nreferenced; i++, referenced++)
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
+
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
{
/*
* If the referenced object is pinned by the system, there's no real
@@ -94,30 +148,34 @@ recordMultipleDependencies(const ObjectAddress *depender,
* Record the Dependency.  Note we don't bother to check for
* duplicate dependencies; there's no harm in them.
*/
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
-
values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);

values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);

-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+
+			tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreHeapTuple(heap_copytuple(tuple), slot[ntuples], false);
+			ntuples++;

Same concern as in the equivalent pg_attribute code.

+	ntuples = 0;
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+									&TTSOpsHeapTuple);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(heap_copytuple(newtuple), slot[ntuples], false);
+		ntuples++;
-		heap_freetuple(newtup);
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
}

Too much copying again.

Greetings,

Andres Freund

#17Michael Paquier
michael@paquier.xyz
In reply to: Andres Freund (#16)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Tue, Nov 12, 2019 at 10:33:16AM -0800, Andres Freund wrote:

On 2019-11-12 16:25:06 +0100, Daniel Gustafsson wrote:

On 11 Nov 2019, at 09:32, Michael Paquier <michael@paquier.xyz> wrote:

This part has resulted in 75c1921, and we could just change
DecodeMultiInsert() so as if there is no tuple data then we'd just
leave. However, I don't feel completely comfortable with that either
as it would be nice to still check that for normal relations we
*always* have a FPW available.

XLH_INSERT_CONTAINS_NEW_TUPLE will only be set in case of catalog relations
IIUC (that is, non logically decoded relations), so it seems to me that we can
have a fastpath out of DecodeMultiInsert() which inspects that flag without
problems. Is this proposal along the lines of what you were thinking?

Maybe I'm missing something, but it's the opposite, no?

bool need_tuple_data = RelationIsLogicallyLogged(relation);

Yep. This checks after IsCatalogRelation().

...
if (need_tuple_data)
xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;

or am I misunderstanding what you mean?

Not sure that I can think about a good way to properly track if the
new tuple data is associated to a catalog or not, aka if the data is
expected all the time or not to get a good sanity check when doing the
multi-insert decoding. We could extend xl_multi_insert_tuple with a
flag to track that, but that seems like an overkill for a simple
sanity check..
--
Michael

#18Andres Freund
andres@anarazel.de
In reply to: Michael Paquier (#17)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

Hi,

On 2019-11-13 15:58:28 +0900, Michael Paquier wrote:

On Tue, Nov 12, 2019 at 10:33:16AM -0800, Andres Freund wrote:

On 2019-11-12 16:25:06 +0100, Daniel Gustafsson wrote:

On 11 Nov 2019, at 09:32, Michael Paquier <michael@paquier.xyz> wrote:

This part has resulted in 75c1921, and we could just change
DecodeMultiInsert() so as if there is no tuple data then we'd just
leave. However, I don't feel completely comfortable with that either
as it would be nice to still check that for normal relations we
*always* have a FPW available.

XLH_INSERT_CONTAINS_NEW_TUPLE will only be set in case of catalog relations
IIUC (that is, non logically decoded relations), so it seems to me that we can
have a fastpath out of DecodeMultiInsert() which inspects that flag without
problems. Is this proposal along the lines of what you were thinking?

Maybe I'm missing something, but it's the opposite, no?

bool need_tuple_data = RelationIsLogicallyLogged(relation);

Yep. This checks after IsCatalogRelation().

...
if (need_tuple_data)
xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;

or am I misunderstanding what you mean?

Not sure that I can think about a good way to properly track if the
new tuple data is associated to a catalog or not, aka if the data is
expected all the time or not to get a good sanity check when doing the
multi-insert decoding. We could extend xl_multi_insert_tuple with a
flag to track that, but that seems like an overkill for a simple
sanity check..

My point was that I think there must be negation missing in Daniel's
statement - XLH_INSERT_CONTAINS_NEW_TUPLE will only be set if *not* a
catalog relation. But Daniel's statement says exactly the opposite, at
least by my read.

I can't follow what you're trying to get at in this sub discussion - why
would we want to sanity check something about catalog tables here? Given
that XLH_INSERT_CONTAINS_NEW_TUPLE is set exactly when the table is not
a catalog table, that seems entirely superfluous?

Greetings,

Andres Freund

#19Michael Paquier
michael@paquier.xyz
In reply to: Andres Freund (#18)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Wed, Nov 13, 2019 at 05:55:12PM -0800, Andres Freund wrote:

My point was that I think there must be negation missing in Daniel's
statement - XLH_INSERT_CONTAINS_NEW_TUPLE will only be set if *not* a
catalog relation. But Daniel's statement says exactly the opposite, at
least by my read.

FWIW, I am reading the same, aka the sentence of Daniel is wrong. And
that what you say is right.

I can't follow what you're trying to get at in this sub discussion - why
would we want to sanity check something about catalog tables here? Given
that XLH_INSERT_CONTAINS_NEW_TUPLE is set exactly when the table is not
a catalog table, that seems entirely superfluous?

[ ... Looking ... ]
You are right, we could just rely on cross-checking that when we have
no data then XLH_INSERT_CONTAINS_NEW_TUPLE is not set, or something
like that:
@@ -901,11 +901,17 @@ DecodeMultiInsert(LogicalDecodingContext *ctx,
XLogRecordBuffer *buf)
return;

    /*
-    * As multi_insert is not used for catalogs yet, the block should always
-    * have data even if a full-page write of it is taken.
+    * multi_insert can be used by catalogs, hence it is possible that
+    * the block does not have any data even if a full-page write of it
+    * is taken.
     */
     tupledata = XLogRecGetBlockData(r, 0, &tuplelen);
-    Assert(tupledata != NULL);
+    Assert(tupledata == NULL ||
+           (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE) != 0);
+
+    /* No data, then leave */
+    if (tupledata == NULL)
+        return;

The latest patch does not apply, so it needs a rebase.
--
Michael

#20Daniel Gustafsson
daniel@yesql.se
In reply to: Andres Freund (#16)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 12 Nov 2019, at 19:33, Andres Freund <andres@anarazel.de> wrote:

Thanks for reviewing!

On 2019-11-12 16:25:06 +0100, Daniel Gustafsson wrote:

On 11 Nov 2019, at 09:32, Michael Paquier <michael@paquier.xyz> wrote:

This part has resulted in 75c1921, and we could just change
DecodeMultiInsert() so as if there is no tuple data then we'd just
leave. However, I don't feel completely comfortable with that either
as it would be nice to still check that for normal relations we
*always* have a FPW available.

XLH_INSERT_CONTAINS_NEW_TUPLE will only be set in case of catalog relations
IIUC (that is, non logically decoded relations), so it seems to me that we can
have a fastpath out of DecodeMultiInsert() which inspects that flag without
problems. Is this proposal along the lines of what you were thinking?

Maybe I'm missing something, but it's the opposite, no?
...
or am I misunderstanding what you mean?

Correct, as has been discussed in this thread already, I managed to write it
backwards.

@@ -1600,10 +1598,16 @@ recordDependencyOnExpr(const ObjectAddress *depender,
/* Remove any duplicates */
eliminate_duplicate_dependencies(context.addrs);

-	/* And record 'em */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/*
+	 * And record 'em. If we know we only have a single dependency then
+	 * avoid the extra cost of setting up a multi insert.
+	 */
+	if (context.addrs->numrefs == 1)
+		recordDependencyOn(depender, &context.addrs->refs[0], behavior);
+	else
+		recordMultipleDependencies(depender,
+								   context.addrs->refs, context.addrs->numrefs,
+								   behavior);

I'm not sure this is actually a worthwhile complexity to keep. Hard to
believe that setting up a multi-insert is goign to have a significant
enough overhead to matter here?

And if it does, is there a chance we can hide this repeated block
somewhere within recordMultipleDependencies() or such? I don't like the
repetitiveness here. Seems recordMultipleDependencies() could easily
optimize the case of there being exactly one dependency to insert?

Agreed, I've simplified by just calling recordMultipleDepencies() until that's
found to be too expensive.

+ slot = palloc(sizeof(TupleTableSlot *) * natts);

Hm. Looking at

SELECT avg(pg_column_size(pa)) FROM pg_attribute pa;

yielding ~144 bytes, we can probably cap this at 128 or such, without
loosing efficiency. Or just use
#define MAX_BUFFERED_BYTES 65535
from copy.c or such (MAX_BUFFERED_BYTES / sizeof(FormData_pg_attribute)).

Added a limit using MAX_BUFFERED_BYTES as above, or the number of tuples,
whichever is smallest.

+ /* This is a tad tedious, but way cleaner than what we used to do... */

I don't like comments that refer to "what we used to" because there's no
way for anybody to make sense of that, because it's basically a dangling
reference :)

This is a copy/paste from InsertPgAttributeTuple added in 03e5248d0f0, which in
turn quite likely was a copy/paste from InsertPgClassTuple added in b7b78d24f7f
back in 2006. Looking at that commit, it's clear what the comment refers to
but it's quite useless in isolation. Since the current coding is its teens by
now, perhaps we should just remove the two existing occurrences?

I can submit a rewrite of the comments into something less gazing into the past
if you feel like removing these.

This seems likely to waste some effort - during insertion the slot will
be materialized, which'll copy the tuple. I think it'd be better to
construct the tuple directly inside the slot's tts_values/isnull, and
then store it with ExecStoreVirtualTuple().

I'm not sure why it looked that way, but it's clearly rubbish. Rewrote by
taking the TupleDesc as input (which addresses your other comment below too),
and create the tuples directly by using ExecStoreVirtualTuple.

+	}
+
+	/* finally insert the new tuples, update the indexes, and clean up */
+	CatalogMultiInsertWithInfo(pg_attribute_rel, slot, natts, indstate);

Previous comment:

I think it might be worthwhile to clear and delete the slots
after inserting. That's potentially a good bit of memory, no?

Current comment:

I think I quite dislike the API of CatalogMultiInsertWithInfo freeing
the slots. It'd e.g. make it impossible to reuse them to insert more
data. It's also really hard to understand

I don't have strong feelings, I see merit in both approches but the reuse
aspect is clearly the winner. I've changed it such that the caller is
responsible for freeing.

+ attrs = palloc(sizeof(Form_pg_attribute) * natts);

Hm. Why we we need this separate allocation? Isn't this exactly the same
as what's in the tupledesc?

Fixed.

+/*
+ * CatalogMultiInsertWithInfo

Hm. The current function is called CatalogTupleInsert(), wouldn't
CatalogTuplesInsertWithInfo() or such be more accurate? Or
CatalogTuplesMultiInsertWithInfo()?

Fixed by opting for the latter, mostly since it seems best convey what the
function does.

Same concern as in the equivalent pg_attribute code.

Too much copying again.

Both of them fixed.

The attached patch addresses all of the comments, thanks again for reviewing!

cheers ./daniel

Attachments:

catalog_multi_insert-v6.patchapplication/octet-stream; name=catalog_multi_insert-v6.patch; x-unix-mode=0644Download
From 71eed733be352b305f1cbdc592de711521aaa033 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Tue, 12 Nov 2019 15:14:10 +0100
Subject: [PATCH] WIP: Use heap_multi_insert for catalog relations, v6

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/catalog/dependency.c           |  30 ++--
 src/backend/catalog/heap.c                 | 159 ++++++++++++++++-----
 src/backend/catalog/index.c                |  43 +++---
 src/backend/catalog/indexing.c             |  36 +++++
 src/backend/catalog/pg_aggregate.c         |  69 ++-------
 src/backend/catalog/pg_constraint.c        |  75 +++-------
 src/backend/catalog/pg_depend.c            |  96 ++++++++++---
 src/backend/catalog/pg_operator.c          |  58 ++------
 src/backend/catalog/pg_proc.c              |  50 ++-----
 src/backend/catalog/pg_shdepend.c          |  30 +++-
 src/backend/catalog/pg_type.c              |  51 +++----
 src/backend/commands/extension.c           |  21 ++-
 src/backend/replication/logical/decode.c   |  62 ++++----
 src/include/catalog/dependency.h           |   5 +
 src/include/catalog/heap.h                 |   5 +-
 src/include/catalog/indexing.h             |   5 +
 src/test/regress/expected/create_index.out |   6 +-
 17 files changed, 438 insertions(+), 363 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d07bb4496e..45264337f9 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -205,8 +205,6 @@ static bool find_expr_references_walker(Node *node,
 										find_expr_references_context *context);
 static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
 static int	object_address_comparator(const void *a, const void *b);
-static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
-							   ObjectAddresses *addrs);
 static void add_exact_object_address_extra(const ObjectAddress *object,
 										   const ObjectAddressExtra *extra,
 										   ObjectAddresses *addrs);
@@ -1601,9 +1599,8 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	eliminate_duplicate_dependencies(context.addrs);
 
 	/* And record 'em */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	recordMultipleDependencies(depender, context.addrs->refs,
+							   context.addrs->numrefs, behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -1708,9 +1705,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 	}
 
 	/* Record the external dependencies */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	recordMultipleDependencies(depender, context.addrs->refs,
+							   context.addrs->numrefs, behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -2420,7 +2416,7 @@ new_object_addresses(void)
  * It is convenient to specify the class by ObjectClass rather than directly
  * by catalog OID.
  */
-static void
+void
 add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
 				   ObjectAddresses *addrs)
 {
@@ -2668,8 +2664,8 @@ record_object_address_dependencies(const ObjectAddress *depender,
 								   DependencyType behavior)
 {
 	eliminate_duplicate_dependencies(referenced);
-	recordMultipleDependencies(depender,
-							   referenced->refs, referenced->numrefs,
+
+	recordMultipleDependencies(depender, referenced->refs, referenced->numrefs,
 							   behavior);
 }
 
@@ -2690,6 +2686,18 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9d0f..4e16f62d14 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -677,6 +677,108 @@ CheckAttributeType(const char *attname,
 				 errhint("Use the COLLATE clause to set the collation explicitly.")));
 }
 
+/*
+ * TODO this is defined in copy.c, if we want to use this to limit the number
+ * of slots in this patch, we need to figure out where to put it.
+ */
+#define MAX_BUFFERED_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
+ *
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
+ */
+void
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						CatalogIndexState indstate)
+{
+	TupleTableSlot	  **slot;
+	TupleDesc			td;
+	int					nslots;
+	int					slotCount;
+	int					natt;
+
+	td = RelationGetDescr(pg_attribute_rel);
+
+	/*
+	 * Allocate a set of slots in which to store the tuples to insert. In case
+	 * we have a fewer tuples than what we can allow in slots, then limit the
+	 * allocation by what we'll actually need.  This allows us to pre-make all
+	 * the slots since we know by definition that we'll be using all of them.
+	 */
+	nslots = Min(tupdesc->natts,
+				 (MAX_BUFFERED_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
+
+	slotCount = 0;
+	natt = 0;
+
+	while (natt < tupdesc->natts)
+	{
+		Form_pg_attribute	new_attributes = TupleDescAttr(tupdesc, natt);
+
+		ExecClearTuple(slot[slotCount]);
+
+		if (new_rel_oid != InvalidOid)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes->attrelid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes->attname);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes->atttypid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes->attstattarget);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes->attlen);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes->attnum);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes->attndims);
+		/* attcacheoff is always -1 in storage */
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes->atttypmod);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes->attbyval);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes->attstorage);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes->attalign);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes->attnotnull);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes->atthasdef);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes->atthasmissing);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes->attidentity);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes->attgenerated);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes->attisdropped);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes->attislocal);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes->attinhcount);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes->attcollation);
+
+		/* start out with empty permissions and empty options */
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/*
+		 * If we've exhausted all the slots for the batch, or we know that
+		 * there are no more tuples, store them in the catalog.
+		 */
+		if (slotCount == nslots || natt == tupdesc->natts - 1)
+		{
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
+
+		natt++;
+	}
+
+	for (int i = 0; i < Min(tupdesc->natts, nslots); i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
+}
+
 /*
  * InsertPgAttributeTuple
  *		Construct and insert a new tuple in pg_attribute.
@@ -754,8 +856,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -769,35 +869,28 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, indstate);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * Now add dependencies on their datatypes and collations.
 	 */
-	for (i = 0; i < natts; i++)
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, indstate);
-
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = tupdesc->attrs[i].atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = tupdesc->attrs[i].attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -810,17 +903,10 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
-
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
+		TupleDesc td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
@@ -3422,6 +3508,7 @@ StorePartitionKey(Relation rel,
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3469,27 +3556,25 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
-
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_OPCLASS, partopclass[i], 0, refobjs);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
-
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_COLLATION, partcollation[i], 0, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * The partitioning columns are made internally dependent on the table,
 	 * because we cannot drop any of them without dropping the whole table.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 67f637de11..ba3f2c04c7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -504,14 +504,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -1026,11 +1019,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1069,12 +1065,9 @@ index_create(Relation heapRelation,
 			{
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					add_object_address(OCLASS_CLASS, heapRelationId,
+									   indexInfo->ii_IndexAttrNumbers[i],
+									   refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1093,6 +1086,11 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+			{
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
+				reset_object_addresses(refobjs);
+			}
 		}
 
 		/*
@@ -1123,23 +1121,16 @@ index_create(Relation heapRelation,
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				add_object_address(OCLASS_COLLATION, collationObjectId[i], 0,
+								   refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
-		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			add_object_address(OCLASS_OPCLASS, classObjectId[i], 0, refobjs);
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
@@ -1160,6 +1151,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index f237e62bc9..f4a1395088 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 1ac235a0f4..a4b31cc30b 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -104,8 +104,8 @@ AggregateCreate(const char *aggName,
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -740,83 +740,44 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_PROC, transfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, finalfn, 0, refobjs);
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, combinefn, 0, refobjs);
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, serialfn, 0, refobjs);
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, deserialfn, 0, refobjs);
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mtransfn, 0, refobjs);
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, minvtransfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mfinalfn, 0, refobjs);
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
-	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_OPERATOR, sortop, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 	return myself;
 }
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 56568b0105..68f1313ec4 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -92,6 +92,7 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -230,30 +231,22 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
-			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
-			}
+				add_object_address(OCLASS_CLASS, relId, constraintKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			add_object_address(OCLASS_CLASS, relId, 0, refobjs);
 		}
 	}
 
@@ -262,39 +255,27 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
-
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
+		add_object_address(OCLASS_TYPE, domainId, 0, refobjs);
 	}
 
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
+	reset_object_addresses(refobjs);
+
 	if (OidIsValid(foreignRelId))
 	{
 		/*
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
-			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_CLASS, foreignRelId, foreignKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_CLASS, foreignRelId, 0, refobjs);
 		}
 	}
 
@@ -306,13 +287,7 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_CLASS, indexRelId, 0, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -323,28 +298,22 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_OPERATOR, pfEqOp[i], 0, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ppEqOp[i], 0, refobjs);
+
 			if (ffEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ffEqOp[i], 0, refobjs);
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index a060c25d2e..364165128f 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,10 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
-	bool		nulls[Natts_pg_depend];
-	Datum		values[Natts_pg_depend];
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -79,9 +123,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -90,34 +135,47 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		 */
 		if (!isObjectPinned(referenced, dependDesc))
 		{
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+			ExecClearTuple(slot[ntuples]);
 			/*
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+			slot[ntuples]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
-			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
-			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
 
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+			memset(slot[ntuples]->tts_isnull, false,
+				   slot[ntuples]->tts_tupleDescriptor->natts * sizeof(bool));
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreVirtualTuple(slot[ntuples]);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
+
+	for (int i = 0; i < ntuples; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index bcaa26c997..b37f01df09 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,15 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -792,39 +794,19 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
-	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_SCHEMA, oper->oprnamespace, 0, refobjs);
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprleft, 0, refobjs);
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprright, 0, refobjs);
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprresult, 0, refobjs);
 
 	/*
 	 * NOTE: we do not consider the operator to depend on the associated
@@ -837,30 +819,18 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprcode, 0, refobjs);
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprrest, 0, refobjs);
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprjoin, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index ef009ad2bc..e81c78f7d3 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -114,8 +114,8 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	int			i;
 	Oid			trfid;
 
@@ -611,49 +611,29 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, procNamespace, 0, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_LANGUAGE, languageObjectId, 0, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_TYPE, returnType, 0, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
-	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_TYPE, allParams[i], 0, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
-		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+			add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 	}
 
 	/* dependency on parameter default expressions */
@@ -663,12 +643,10 @@ ProcedureCreate(const char *procedureName,
 
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, prosupport, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* dependency on owner */
 	if (!is_update)
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 59f97bf3d0..2012e75fce 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -799,10 +799,15 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	HeapTuple	newtuple;
+	int			ntuples;
 	CatalogIndexState indstate;
 	Datum		values[Natts_pg_shdepend];
 	bool		nulls[Natts_pg_shdepend];
 	bool		replace[Natts_pg_shdepend];
+	/* TODO figure out a sensible value for the amount of slots */
+#define	DEPEND_TUPLE_BUF 32
+	TupleTableSlot *slot[DEPEND_TUPLE_BUF];
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
@@ -833,16 +838,29 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	ntuples = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
-
-		heap_freetuple(newtup);
+		/* Dont create if already created */
+		if (!slot[ntuples])
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+													 &TTSOpsHeapTuple);
+		ExecClearTuple(slot[ntuples]);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(newtuple, slot[ntuples], false);
+		ntuples++;
+
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (ntuples)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index a8c1de511f..3793092ae5 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -544,7 +544,9 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool rebuild)
 {
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
@@ -579,63 +581,54 @@ GenerateTypeDependencies(Oid typeObjectId,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index a04b0c9e57..5bac394c3b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1771,7 +1771,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	bool		nulls[Natts_pg_extension];
 	HeapTuple	tuple;
 	ObjectAddress myself;
-	ObjectAddress nsp;
+	ObjectAddresses *refobjs;
 	ListCell   *lc;
 
 	/*
@@ -1812,29 +1812,26 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	/*
 	 * Record dependencies on owner, schema, and prerequisite extensions
 	 */
+	refobjs = new_object_addresses();
+
 	recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
 
 	myself.classId = ExtensionRelationId;
 	myself.objectId = extensionOid;
 	myself.objectSubId = 0;
 
-	nsp.classId = NamespaceRelationId;
-	nsp.objectId = schemaOid;
-	nsp.objectSubId = 0;
-
-	recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, schemaOid, 0, refobjs);
 
 	foreach(lc, requiredExtensions)
 	{
 		Oid			reqext = lfirst_oid(lc);
-		ObjectAddress otherext;
-
-		otherext.classId = ExtensionRelationId;
-		otherext.objectId = reqext;
-		otherext.objectSubId = 0;
 
-		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_EXTENSION, reqext, 0, refobjs);
 	}
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index bc532d027b..46ebf05463 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -891,6 +891,14 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
 
+	/*
+	 * CONTAINS_NEW_TUPLE will always be set unless the multi_insert was
+	 * performed for a catalog.  If it is a catalog, return immediately as
+	 * there is nothing to logically decode.
+	 */
+	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
+		return;
+
 	/* only interested in our database */
 	XLogRecGetBlockTag(r, 0, &rnode, NULL, NULL);
 	if (rnode.dbNode != ctx->slot->data.database)
@@ -914,6 +922,7 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		xl_multi_insert_tuple *xlhdr;
 		int			datalen;
 		ReorderBufferTupleBuf *tuple;
+		HeapTupleHeader header;
 
 		change = ReorderBufferGetChange(ctx->reorder);
 		change->action = REORDER_BUFFER_CHANGE_INSERT;
@@ -925,43 +934,31 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		data = ((char *) xlhdr) + SizeOfMultiInsertTuple;
 		datalen = xlhdr->datalen;
 
-		/*
-		 * CONTAINS_NEW_TUPLE will always be set currently as multi_insert
-		 * isn't used for catalogs, but better be future proof.
-		 *
-		 * We decode the tuple in pretty much the same way as DecodeXLogTuple,
-		 * but since the layout is slightly different, we can't use it here.
-		 */
-		if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)
-		{
-			HeapTupleHeader header;
-
-			change->data.tp.newtuple =
-				ReorderBufferGetTupleBuf(ctx->reorder, datalen);
+		change->data.tp.newtuple =
+			ReorderBufferGetTupleBuf(ctx->reorder, datalen);
 
-			tuple = change->data.tp.newtuple;
-			header = tuple->tuple.t_data;
+		tuple = change->data.tp.newtuple;
+		header = tuple->tuple.t_data;
 
-			/* not a disk based tuple */
-			ItemPointerSetInvalid(&tuple->tuple.t_self);
+		/* not a disk based tuple */
+		ItemPointerSetInvalid(&tuple->tuple.t_self);
 
-			/*
-			 * We can only figure this out after reassembling the
-			 * transactions.
-			 */
-			tuple->tuple.t_tableOid = InvalidOid;
+		/*
+		 * We can only figure this out after reassembling the
+		 * transactions.
+		 */
+		tuple->tuple.t_tableOid = InvalidOid;
 
-			tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
+		tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
 
-			memset(header, 0, SizeofHeapTupleHeader);
+		memset(header, 0, SizeofHeapTupleHeader);
 
-			memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
-				   (char *) data,
-				   datalen);
-			header->t_infomask = xlhdr->t_infomask;
-			header->t_infomask2 = xlhdr->t_infomask2;
-			header->t_hoff = xlhdr->t_hoff;
-		}
+		memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
+			   (char *) data,
+			   datalen);
+		header->t_infomask = xlhdr->t_infomask;
+		header->t_infomask2 = xlhdr->t_infomask2;
+		header->t_hoff = xlhdr->t_hoff;
 
 		/*
 		 * Reset toast reassembly state only after the last row in the last
@@ -980,7 +977,8 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		/* move to the next xl_multi_insert_tuple entry */
 		data += datalen;
 	}
-	Assert(data == tupledata + tuplelen);
+	Assert(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE &&
+		   data == tupledata + tuplelen);
 }
 
 /*
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index ff50d594f6..7e3bcde2ea 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -162,6 +162,9 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
 
 extern ObjectAddresses *new_object_addresses(void);
 
+extern void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
+							   ObjectAddresses *addrs);
+
 extern void add_exact_object_address(const ObjectAddress *object,
 									 ObjectAddresses *addrs);
 
@@ -174,6 +177,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index eec71c29d5..2e1d1a6e31 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -95,7 +95,10 @@ extern List *heap_truncate_find_FKs(List *relationIds);
 extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
 								   Form_pg_attribute new_attribute,
 								   CatalogIndexState indstate);
-
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									CatalogIndexState indstate);
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
 							   Oid new_rel_oid,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b017..38fb6d3c68 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 1cdb7a9663..8a4b9cb7cd 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1994,11 +1994,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2022,11 +2021,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
-- 
2.21.0 (Apple Git-122.2)

#21Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#20)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Sun, Nov 17, 2019 at 12:01:08AM +0100, Daniel Gustafsson wrote:

Fixed by opting for the latter, mostly since it seems best convey what the
function does.

-   recordMultipleDependencies(depender,
-                              context.addrs->refs, context.addrs->numrefs,
-                              behavior);
+   recordMultipleDependencies(depender, context.addrs->refs,
+                              context.addrs->numrefs, behavior);
Some noise diffs.
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
  index concur_reindex_ind4                | column c1 of table
- index concur_reindex_ind4                | column c1 of table
  index concur_reindex_ind4                | column c2 of table
This removes a duplicated dependency with indexes using the same
column multiple times.  Guess that's good to get rid of, this was just
unnecessary bloat in pg_depend.

The regression tests of contrib/test_decoding are still failing here:
+ERROR: could not resolve cmin/cmax of catalog tuple

Getting rid the duplication between InsertPgAttributeTuples() and
InsertPgAttributeTuple() would be nice. You would basically finish by
just using a single slot when inserting one tuple..
--
Michael

#22Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#21)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 26 Nov 2019, at 06:44, Michael Paquier <michael@paquier.xyz> wrote:

Re this patch being in WoA state for some time [0]postgr.es/m/20200121144937.n24oacjkegu4pnpe@development:

The regression tests of contrib/test_decoding are still failing here:
+ERROR: could not resolve cmin/cmax of catalog tuple

This is the main thing left with this patch, and I've been unable so far to
figure it out. I have an unscientific hunch that this patch is shaking out
something (previously unexercised) in the logical decoding code supporting
multi-inserts in the catalog. If anyone has ideas or insights, I would love
the help.

Once my plate clears up a bit I will return to this one, but feel free to mark
it rwf for this cf.

cheers ./daniel

[0]: postgr.es/m/20200121144937.n24oacjkegu4pnpe@development

#23Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#22)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Wed, Jan 22, 2020 at 11:18:12PM +0100, Daniel Gustafsson wrote:

Once my plate clears up a bit I will return to this one, but feel free to mark
it rwf for this cf.

Thanks for the update. I have switched the patch status to returned
with feedback.
--
Michael

#24Andres Freund
andres@anarazel.de
In reply to: Daniel Gustafsson (#22)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

Hi,

On 2020-01-22 23:18:12 +0100, Daniel Gustafsson wrote:

On 26 Nov 2019, at 06:44, Michael Paquier <michael@paquier.xyz> wrote:

Re this patch being in WoA state for some time [0]:

The regression tests of contrib/test_decoding are still failing here:
+ERROR: could not resolve cmin/cmax of catalog tuple

This is the main thing left with this patch, and I've been unable so far to
figure it out. I have an unscientific hunch that this patch is shaking out
something (previously unexercised) in the logical decoding code supporting
multi-inserts in the catalog. If anyone has ideas or insights, I would love
the help.

Perhaps we can take a look at it together while at fosdem? Feel free to
remind me...

Greetings,

Andres Freund

#25Daniel Gustafsson
daniel@yesql.se
In reply to: Andres Freund (#24)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 26 Jan 2020, at 21:30, Andres Freund <andres@anarazel.de> wrote:
On 2020-01-22 23:18:12 +0100, Daniel Gustafsson wrote:

On 26 Nov 2019, at 06:44, Michael Paquier <michael@paquier.xyz> wrote:

Re this patch being in WoA state for some time [0]:

The regression tests of contrib/test_decoding are still failing here:
+ERROR: could not resolve cmin/cmax of catalog tuple

This is the main thing left with this patch, and I've been unable so far to
figure it out. I have an unscientific hunch that this patch is shaking out
something (previously unexercised) in the logical decoding code supporting
multi-inserts in the catalog. If anyone has ideas or insights, I would love
the help.

Perhaps we can take a look at it together while at fosdem? Feel free to
remind me...

Which we did, and after that I realized I had been looking at it from the wrong
angle. Returning to it after coming home from FOSDEM I believe I have found
the culprit.

Turns out that we in heap_multi_insert missed to call log_heap_new_cid for the
first tuple inserted, we only do it in the loop body for the subsequent ones.
With the attached patch, the v6 of this patch posted upthead pass the tests for
me. I have a v7 brewing which I'll submit shortly, but since this fix
unrelated to that patchseries other than as a pre-requisite I figured I'd post
that separately.

cheers ./daniel

Attachments:

heap_multi_insert_cid.patchapplication/octet-stream; name=heap_multi_insert_cid.patch; x-unix-mode=0644Download
From 26832e2e81ba68da5f67eab79f301b71d1f3ed57 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 21 Feb 2020 22:43:24 +0100
Subject: [PATCH 2/2] Fix bug in recording CID for first tuple in a
 heap_multi_insert

---
 src/backend/access/heap/heapam.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index db6fad76bc..67cbceb22f 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2191,6 +2191,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		 * Put that on the page, and then as many other tuples as fit.
 		 */
 		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		if (needwal && need_cids)
+			log_heap_new_cid(relation, heaptuples[ndone]);
 		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
 		{
 			HeapTuple	heaptup = heaptuples[ndone + nthispage];
-- 
2.21.1 (Apple Git-122.3)

#26Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#25)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Sat, Feb 22, 2020 at 10:22:27PM +0100, Daniel Gustafsson wrote:

Turns out that we in heap_multi_insert missed to call log_heap_new_cid for the
first tuple inserted, we only do it in the loop body for the subsequent ones.
With the attached patch, the v6 of this patch posted upthead pass the tests for
me. I have a v7 brewing which I'll submit shortly, but since this fix
unrelated to that patchseries other than as a pre-requisite I figured I'd post
that separately.

Good catch. I would not backpatch that as it is not a live bug
because heap_multi_insert() is not used for catalogs yet. With your
patch, that would be the case though..
--
Michael

#27Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#26)
2 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 23 Feb 2020, at 08:27, Michael Paquier <michael@paquier.xyz> wrote:

On Sat, Feb 22, 2020 at 10:22:27PM +0100, Daniel Gustafsson wrote:

Turns out that we in heap_multi_insert missed to call log_heap_new_cid for the
first tuple inserted, we only do it in the loop body for the subsequent ones.
With the attached patch, the v6 of this patch posted upthead pass the tests for
me. I have a v7 brewing which I'll submit shortly, but since this fix
unrelated to that patchseries other than as a pre-requisite I figured I'd post
that separately.

Good catch. I would not backpatch that as it is not a live bug
because heap_multi_insert() is not used for catalogs yet. With your
patch, that would be the case though..

I'll leave that call up to others, the bug is indeed unreachable with the
current coding.

Attached is a v7 of the catalog multi_insert patch which removes some code
duplication which was previously commented on. There are still a few rouch
edges but this version passes tests when paired with the heap_multi_insert cid
patch.

cheers ./daniel

Attachments:

catalog_multi_insert-v7.patchapplication/octet-stream; name=catalog_multi_insert-v7.patch; x-unix-mode=0644Download
From d0b25ca2173e00645f48a87e9218277c7bca4fee Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Tue, 12 Nov 2019 15:14:10 +0100
Subject: [PATCH 1/2] WIP: Use heap_multi_insert for catalog relations, v6

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/catalog/dependency.c           |  20 +-
 src/backend/catalog/heap.c                 | 212 ++++++++++++---------
 src/backend/catalog/index.c                |  49 ++---
 src/backend/catalog/indexing.c             |  36 ++++
 src/backend/catalog/pg_aggregate.c         |  69 ++-----
 src/backend/catalog/pg_constraint.c        |  75 +++-----
 src/backend/catalog/pg_depend.c            |  96 ++++++++--
 src/backend/catalog/pg_operator.c          |  58 ++----
 src/backend/catalog/pg_proc.c              |  50 ++---
 src/backend/catalog/pg_shdepend.c          |  30 ++-
 src/backend/catalog/pg_type.c              |  51 +++--
 src/backend/commands/extension.c           |  21 +-
 src/backend/commands/tablecmds.c           |  10 +-
 src/backend/replication/logical/decode.c   |  62 +++---
 src/include/catalog/dependency.h           |   5 +
 src/include/catalog/heap.h                 |   8 +-
 src/include/catalog/indexing.h             |   5 +
 src/test/regress/expected/create_index.out |   6 +-
 18 files changed, 447 insertions(+), 416 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index c4a4df25b8..02578ab090 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -205,8 +205,6 @@ static bool find_expr_references_walker(Node *node,
 										find_expr_references_context *context);
 static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
 static int	object_address_comparator(const void *a, const void *b);
-static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
-							   ObjectAddresses *addrs);
 static void add_exact_object_address_extra(const ObjectAddress *object,
 										   const ObjectAddressExtra *extra,
 										   ObjectAddresses *addrs);
@@ -2432,7 +2430,7 @@ new_object_addresses(void)
  * It is convenient to specify the class by ObjectClass rather than directly
  * by catalog OID.
  */
-static void
+void
 add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
 				   ObjectAddresses *addrs)
 {
@@ -2702,6 +2700,22 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+/*
+ * Clear an ObjectAddresses array such that it can be reused to avoid an
+ * allocation cycle.
+ */
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478bf91..ac3ac770c4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -710,68 +710,115 @@ CheckAttributeType(const char *attname,
 }
 
 /*
- * InsertPgAttributeTuple
- *		Construct and insert a new tuple in pg_attribute.
- *
- * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl and
- * attoptions are always initialized to NULL.
+ * TODO this is defined in copy.c, if we want to use this to limit the number
+ * of slots in this patch, we need to figure out where to put it.
+ */
+#define MAX_BUFFERED_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
  *
- * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
- * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
- * this when inserting multiple attributes, because it's a tad more
- * expensive.)
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
  */
 void
-InsertPgAttributeTuple(Relation pg_attribute_rel,
-					   Form_pg_attribute new_attribute,
-					   CatalogIndexState indstate)
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						CatalogIndexState indstate)
 {
-	Datum		values[Natts_pg_attribute];
-	bool		nulls[Natts_pg_attribute];
-	HeapTuple	tup;
+	TupleTableSlot	  **slot;
+	TupleDesc			td;
+	int					nslots;
+	int					slotCount;
+	int					natt;
+	bool				close_index = false;
 
-	/* This is a tad tedious, but way cleaner than what we used to do... */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
+	td = RelationGetDescr(pg_attribute_rel);
 
-	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
-	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
-	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
-	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
-	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
-	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
-	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
-	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
-	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
-	values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
-	values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
-	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
-	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
-	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
-	values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
-	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
-	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
-	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
-	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
-	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
-	values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
-
-	/* start out with empty permissions and empty options */
-	nulls[Anum_pg_attribute_attacl - 1] = true;
-	nulls[Anum_pg_attribute_attoptions - 1] = true;
-	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
-	nulls[Anum_pg_attribute_attmissingval - 1] = true;
-
-	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+	/*
+	 * Allocate a set of slots in which to store the tuples to insert. In case
+	 * we have a fewer tuples than what we can allow in slots, then limit the
+	 * allocation by what we'll actually need.  This allows us to pre-make all
+	 * the slots since we know by definition that we'll be using all of them.
+	 */
+	nslots = Min(tupdesc->natts,
+				 (MAX_BUFFERED_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
 
-	/* finally insert the new tuple, update the indexes, and clean up */
-	if (indstate != NULL)
-		CatalogTupleInsertWithInfo(pg_attribute_rel, tup, indstate);
-	else
-		CatalogTupleInsert(pg_attribute_rel, tup);
+	slotCount = 0;
+	natt = 0;
 
-	heap_freetuple(tup);
+	while (natt < tupdesc->natts)
+	{
+		Form_pg_attribute	new_attributes = TupleDescAttr(tupdesc, natt);
+
+		ExecClearTuple(slot[slotCount]);
+
+		if (new_rel_oid != InvalidOid)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes->attrelid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes->attname);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes->atttypid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes->attstattarget);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes->attlen);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes->attnum);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes->attndims);
+		/* attcacheoff is always -1 in storage */
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes->atttypmod);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes->attbyval);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes->attstorage);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes->attalign);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes->attnotnull);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes->atthasdef);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes->atthasmissing);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes->attidentity);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes->attgenerated);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes->attisdropped);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes->attislocal);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes->attinhcount);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes->attcollation);
+
+		/* start out with empty permissions and empty options */
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/*
+		 * If we've exhausted all the slots for the batch, or we know that
+		 * there are no more tuples, store them in the catalog.
+		 */
+		if (slotCount == nslots || natt == tupdesc->natts - 1)
+		{
+			if (!indstate)
+			{
+				indstate = CatalogOpenIndexes(pg_attribute_rel);
+				close_index = true;
+			}
+
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
+
+		natt++;
+	}
+
+	if (close_index)
+		CatalogCloseIndexes(indstate);
+
+	for (int i = 0; i < Min(tupdesc->natts, nslots); i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /* --------------------------------
@@ -786,8 +833,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -801,35 +846,28 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, indstate);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * Now add dependencies on their datatypes and collations.
 	 */
-	for (i = 0; i < natts; i++)
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, indstate);
-
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = tupdesc->attrs[i].atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = tupdesc->attrs[i].attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -842,17 +880,10 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
-
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
+		TupleDesc td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
@@ -3540,6 +3571,7 @@ StorePartitionKey(Relation rel,
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3587,27 +3619,25 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
-
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_OPCLASS, partopclass[i], 0, refobjs);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
-
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_COLLATION, partcollation[i], 0, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * The partitioning columns are made internally dependent on the table,
 	 * because we cannot drop any of them without dropping the whole table.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586c37..66c6ecac74 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -105,7 +105,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, int numatts);
+static void AppendAttributeTuples(Relation indexRelation);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -484,12 +484,11 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, int numatts)
+AppendAttributeTuples(Relation indexRelation)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
 	TupleDesc	indexTupDesc;
-	int			i;
 
 	/*
 	 * open the attribute relation and its indexes
@@ -503,14 +502,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -976,7 +968,7 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs);
+	AppendAttributeTuples(indexRelation);
 
 	/* ----------------
 	 *	  update pg_index
@@ -1025,11 +1017,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1068,12 +1063,9 @@ index_create(Relation heapRelation,
 			{
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					add_object_address(OCLASS_CLASS, heapRelationId,
+									   indexInfo->ii_IndexAttrNumbers[i],
+									   refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1092,6 +1084,8 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
 		}
 
 		/*
@@ -1115,30 +1109,25 @@ index_create(Relation heapRelation,
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 		}
 
+
 		/* Store dependency on collations */
 		/* The default collation is pinned, so don't bother recording it */
+		reset_object_addresses(refobjs);
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				add_object_address(OCLASS_COLLATION, collationObjectId[i], 0,
+								   refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
-		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			add_object_address(OCLASS_OPCLASS, classObjectId[i], 0, refobjs);
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
@@ -1159,6 +1148,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index d63fcf58cf..b98d4bffbd 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 0b7face4cc..00b0170914 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -104,8 +104,8 @@ AggregateCreate(const char *aggName,
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -740,83 +740,44 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_PROC, transfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, finalfn, 0, refobjs);
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, combinefn, 0, refobjs);
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, serialfn, 0, refobjs);
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, deserialfn, 0, refobjs);
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mtransfn, 0, refobjs);
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, minvtransfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mfinalfn, 0, refobjs);
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
-	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_OPERATOR, sortop, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 	return myself;
 }
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 3d2b1cc911..334f3d7948 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -91,6 +91,7 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -229,30 +230,22 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
-			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
-			}
+				add_object_address(OCLASS_CLASS, relId, constraintKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			add_object_address(OCLASS_CLASS, relId, 0, refobjs);
 		}
 	}
 
@@ -261,14 +254,13 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
+		add_object_address(OCLASS_TYPE, domainId, 0, refobjs);
+	}
 
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
 
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
-	}
+	reset_object_addresses(refobjs);
 
 	if (OidIsValid(foreignRelId))
 	{
@@ -276,24 +268,15 @@ CreateConstraintEntry(const char *constraintName,
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
-			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_CLASS, foreignRelId, foreignKey[i],
+								   refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_CLASS, foreignRelId, 0, refobjs);
 		}
 	}
 
@@ -305,13 +288,7 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_CLASS, indexRelId, 0, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -322,28 +299,22 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_OPERATOR, pfEqOp[i], 0, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ppEqOp[i], 0, refobjs);
+
 			if (ffEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ffEqOp[i], 0, refobjs);
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index f9af245eec..216be7bea1 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,10 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
-	bool		nulls[Natts_pg_depend];
-	Datum		values[Natts_pg_depend];
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -79,9 +123,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -90,34 +135,47 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		 */
 		if (!isObjectPinned(referenced, dependDesc))
 		{
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+			ExecClearTuple(slot[ntuples]);
 			/*
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+			slot[ntuples]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
-			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
-			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
 
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+			memset(slot[ntuples]->tts_isnull, false,
+				   slot[ntuples]->tts_tupleDescriptor->natts * sizeof(bool));
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreVirtualTuple(slot[ntuples]);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
+
+	for (int i = 0; i < ntuples; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 340e284ae4..f1f985f8bc 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,15 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -792,39 +794,19 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
-	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_SCHEMA, oper->oprnamespace, 0, refobjs);
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprleft, 0, refobjs);
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprright, 0, refobjs);
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprresult, 0, refobjs);
 
 	/*
 	 * NOTE: we do not consider the operator to depend on the associated
@@ -837,30 +819,18 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprcode, 0, refobjs);
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprrest, 0, refobjs);
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprjoin, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 5194dcaac0..4e3927af4b 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -114,8 +114,8 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	int			i;
 	Oid			trfid;
 
@@ -611,49 +611,29 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, procNamespace, 0, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_LANGUAGE, languageObjectId, 0, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_TYPE, returnType, 0, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
-	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_TYPE, allParams[i], 0, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
-		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+			add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 	}
 
 	/* dependency on parameter default expressions */
@@ -663,12 +643,10 @@ ProcedureCreate(const char *procedureName,
 
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, prosupport, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* dependency on owner */
 	if (!is_update)
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 2ef792dbd7..1ab69c3640 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -799,10 +799,15 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	HeapTuple	newtuple;
+	int			ntuples;
 	CatalogIndexState indstate;
 	Datum		values[Natts_pg_shdepend];
 	bool		nulls[Natts_pg_shdepend];
 	bool		replace[Natts_pg_shdepend];
+	/* TODO figure out a sensible value for the amount of slots */
+#define	DEPEND_TUPLE_BUF 32
+	TupleTableSlot *slot[DEPEND_TUPLE_BUF];
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
@@ -833,16 +838,29 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	ntuples = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
-
-		heap_freetuple(newtup);
+		/* Dont create if already created */
+		if (!slot[ntuples])
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+													 &TTSOpsHeapTuple);
+		ExecClearTuple(slot[ntuples]);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(newtuple, slot[ntuples], false);
+		ntuples++;
+
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (ntuples)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 8d7572da51..5dd2b67c5f 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -544,7 +544,9 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool rebuild)
 {
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
@@ -579,63 +581,54 @@ GenerateTypeDependencies(Oid typeObjectId,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index a0db7db411..0295f1a29f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1782,7 +1782,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	bool		nulls[Natts_pg_extension];
 	HeapTuple	tuple;
 	ObjectAddress myself;
-	ObjectAddress nsp;
+	ObjectAddresses *refobjs;
 	ListCell   *lc;
 
 	/*
@@ -1823,29 +1823,26 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	/*
 	 * Record dependencies on owner, schema, and prerequisite extensions
 	 */
+	refobjs = new_object_addresses();
+
 	recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
 
 	myself.classId = ExtensionRelationId;
 	myself.objectId = extensionOid;
 	myself.objectSubId = 0;
 
-	nsp.classId = NamespaceRelationId;
-	nsp.objectId = schemaOid;
-	nsp.objectSubId = 0;
-
-	recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, schemaOid, 0, refobjs);
 
 	foreach(lc, requiredExtensions)
 	{
 		Oid			reqext = lfirst_oid(lc);
-		ObjectAddress otherext;
-
-		otherext.classId = ExtensionRelationId;
-		otherext.objectId = reqext;
-		otherext.objectSubId = 0;
 
-		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_EXTENSION, reqext, 0, refobjs);
 	}
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7c8d663fc..aaa3209e0d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6057,6 +6057,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   0);
 
+	MemoryContext old = MemoryContextSwitchTo(CurTransactionContext);
 	/* construct new attribute's pg_attribute entry */
 	attribute.attrelid = myrelid;
 	namestrcpy(&(attribute.attname), colDef->colname);
@@ -6080,12 +6081,19 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attcollation = collOid;
 	/* attribute.attacl is handled by InsertPgAttributeTuple */
 
+
+	FormData_pg_attribute *aattr[] = {&attribute};
+
 	ReleaseSysCache(typeTuple);
 
-	InsertPgAttributeTuple(attrdesc, &attribute, NULL);
+	TupleDesc tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
+	MemoryContextSwitchTo(old);
+
 	/*
 	 * Update pg_class tuple as appropriate
 	 */
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 5e1dc8a651..7fba4a71ad 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -891,6 +891,14 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
 
+	/*
+	 * CONTAINS_NEW_TUPLE will always be set unless the multi_insert was
+	 * performed for a catalog.  If it is a catalog, return immediately as
+	 * there is nothing to logically decode.
+	 */
+	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
+		return;
+
 	/* only interested in our database */
 	XLogRecGetBlockTag(r, 0, &rnode, NULL, NULL);
 	if (rnode.dbNode != ctx->slot->data.database)
@@ -914,6 +922,7 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		xl_multi_insert_tuple *xlhdr;
 		int			datalen;
 		ReorderBufferTupleBuf *tuple;
+		HeapTupleHeader header;
 
 		change = ReorderBufferGetChange(ctx->reorder);
 		change->action = REORDER_BUFFER_CHANGE_INSERT;
@@ -925,43 +934,31 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		data = ((char *) xlhdr) + SizeOfMultiInsertTuple;
 		datalen = xlhdr->datalen;
 
-		/*
-		 * CONTAINS_NEW_TUPLE will always be set currently as multi_insert
-		 * isn't used for catalogs, but better be future proof.
-		 *
-		 * We decode the tuple in pretty much the same way as DecodeXLogTuple,
-		 * but since the layout is slightly different, we can't use it here.
-		 */
-		if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)
-		{
-			HeapTupleHeader header;
-
-			change->data.tp.newtuple =
-				ReorderBufferGetTupleBuf(ctx->reorder, datalen);
+		change->data.tp.newtuple =
+			ReorderBufferGetTupleBuf(ctx->reorder, datalen);
 
-			tuple = change->data.tp.newtuple;
-			header = tuple->tuple.t_data;
+		tuple = change->data.tp.newtuple;
+		header = tuple->tuple.t_data;
 
-			/* not a disk based tuple */
-			ItemPointerSetInvalid(&tuple->tuple.t_self);
+		/* not a disk based tuple */
+		ItemPointerSetInvalid(&tuple->tuple.t_self);
 
-			/*
-			 * We can only figure this out after reassembling the
-			 * transactions.
-			 */
-			tuple->tuple.t_tableOid = InvalidOid;
+		/*
+		 * We can only figure this out after reassembling the
+		 * transactions.
+		 */
+		tuple->tuple.t_tableOid = InvalidOid;
 
-			tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
+		tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
 
-			memset(header, 0, SizeofHeapTupleHeader);
+		memset(header, 0, SizeofHeapTupleHeader);
 
-			memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
-				   (char *) data,
-				   datalen);
-			header->t_infomask = xlhdr->t_infomask;
-			header->t_infomask2 = xlhdr->t_infomask2;
-			header->t_hoff = xlhdr->t_hoff;
-		}
+		memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
+			   (char *) data,
+			   datalen);
+		header->t_infomask = xlhdr->t_infomask;
+		header->t_infomask2 = xlhdr->t_infomask2;
+		header->t_hoff = xlhdr->t_hoff;
 
 		/*
 		 * Reset toast reassembly state only after the last row in the last
@@ -980,7 +977,8 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		/* move to the next xl_multi_insert_tuple entry */
 		data += datalen;
 	}
-	Assert(data == tupledata + tuplelen);
+	Assert(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE &&
+		   data == tupledata + tuplelen);
 }
 
 /*
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 0cd6fcf027..0f8084812d 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -162,6 +162,9 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
 
 extern ObjectAddresses *new_object_addresses(void);
 
+extern void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
+							   ObjectAddresses *addrs);
+
 extern void add_exact_object_address(const ObjectAddress *object,
 									 ObjectAddresses *addrs);
 
@@ -174,6 +177,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index bd64024946..5271489a6e 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -93,10 +93,10 @@ extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
 extern List *heap_truncate_find_FKs(List *relationIds);
 
-extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
-								   Form_pg_attribute new_attribute,
-								   CatalogIndexState indstate);
-
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									CatalogIndexState indstate);
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
 							   Oid new_rel_oid,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..a7e2a9b26b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 6ddf3a63c3..f08ac5efa5 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2069,11 +2069,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2097,11 +2096,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
-- 
2.21.1 (Apple Git-122.3)

heap_multi_insert_cid.patchapplication/octet-stream; name=heap_multi_insert_cid.patch; x-unix-mode=0644Download
From 26832e2e81ba68da5f67eab79f301b71d1f3ed57 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 21 Feb 2020 22:43:24 +0100
Subject: [PATCH 2/2] Fix bug in recording CID for first tuple in a
 heap_multi_insert

---
 src/backend/access/heap/heapam.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index db6fad76bc..67cbceb22f 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2191,6 +2191,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		 * Put that on the page, and then as many other tuples as fit.
 		 */
 		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		if (needwal && need_cids)
+			log_heap_new_cid(relation, heaptuples[ndone]);
 		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
 		{
 			HeapTuple	heaptup = heaptuples[ndone + nthispage];
-- 
2.21.1 (Apple Git-122.3)

#28Andres Freund
andres@anarazel.de
In reply to: Michael Paquier (#26)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

Hi,

On 2020-02-23 16:27:57 +0900, Michael Paquier wrote:

On Sat, Feb 22, 2020 at 10:22:27PM +0100, Daniel Gustafsson wrote:

Turns out that we in heap_multi_insert missed to call log_heap_new_cid for the
first tuple inserted, we only do it in the loop body for the subsequent ones.
With the attached patch, the v6 of this patch posted upthead pass the tests for
me. I have a v7 brewing which I'll submit shortly, but since this fix
unrelated to that patchseries other than as a pre-requisite I figured I'd post
that separately.

Thanks for finding!

Good catch. I would not backpatch that as it is not a live bug
because heap_multi_insert() is not used for catalogs yet. With your
patch, that would be the case though..

Thanks for pushing.

I do find it a bit odd that you essentially duplicated the existing
comment in a different phrasing. What's the point?

Greetings,

Andres Freund

#29Michael Paquier
michael@paquier.xyz
In reply to: Andres Freund (#28)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Mon, Feb 24, 2020 at 03:29:03PM -0800, Andres Freund wrote:

I do find it a bit odd that you essentially duplicated the existing
comment in a different phrasing. What's the point?

A direct reference to catalog tuples is added for each code path
calling log_heap_new_cid(), so that was more natural to me.
--
Michael

#30Daniel Gustafsson
daniel@yesql.se
In reply to: Andres Freund (#28)
2 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 25 Feb 2020, at 00:29, Andres Freund <andres@anarazel.de> wrote:
On 2020-02-23 16:27:57 +0900, Michael Paquier wrote:

Good catch. I would not backpatch that as it is not a live bug
because heap_multi_insert() is not used for catalogs yet. With your
patch, that would be the case though..

Thanks for pushing.

+1, thanks to the both of you for helping with the patch. Attached is a v8 of
the patchset to make the cfbot happier, as the previous no longer applies.

In doing that I realized that there is another hunk in this patch for fixing up
logical decoding multi-insert support, which is independent of the patch in
question here so I split it off. It's another issue which cause no harm at all
today, but fails as soon as someone tries to perform multi inserts into the
catalog.

AFAICT, the current coding assumes that the multi-insert isn't from a catalog,
and makes little attempts at surviving in case it is. The attached patch fixes
that by taking a fast-path exit in case it is a catalog multi-insert, as there
is nothing to decode.

cheers ./daniel

Attachments:

catalog_multi_insert-v8.patchapplication/octet-stream; name=catalog_multi_insert-v8.patch; x-unix-mode=0644Download
From 06437db90e4a81f1cfbb8570552777d4b482489c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Tue, 12 Nov 2019 15:14:10 +0100
Subject: [PATCH 1/2] WIP: Use heap_multi_insert for catalog relations, v8

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once, and replaces
InsertPgAttributeTuple.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/access/heap/heapam.c           |   4 -
 src/backend/catalog/dependency.c           |  20 +-
 src/backend/catalog/heap.c                 | 212 ++++++++++++---------
 src/backend/catalog/index.c                |  49 ++---
 src/backend/catalog/indexing.c             |  36 ++++
 src/backend/catalog/pg_aggregate.c         |  69 ++-----
 src/backend/catalog/pg_constraint.c        |  75 +++-----
 src/backend/catalog/pg_depend.c            |  96 ++++++++--
 src/backend/catalog/pg_operator.c          |  58 ++----
 src/backend/catalog/pg_proc.c              |  50 ++---
 src/backend/catalog/pg_shdepend.c          |  30 ++-
 src/backend/catalog/pg_type.c              |  51 +++--
 src/backend/commands/extension.c           |  21 +-
 src/backend/commands/tablecmds.c           |  10 +-
 src/include/catalog/dependency.h           |   5 +
 src/include/catalog/heap.h                 |   8 +-
 src/include/catalog/indexing.h             |   5 +
 src/test/regress/expected/create_index.out |   6 +-
 18 files changed, 417 insertions(+), 388 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 5a32e62ed0..fbbf238ecf 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2208,10 +2208,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			RelationPutHeapTuple(relation, buffer, heaptup, false);
 
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
 			if (needwal && need_cids)
 				log_heap_new_cid(relation, heaptup);
 		}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index c4a4df25b8..02578ab090 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -205,8 +205,6 @@ static bool find_expr_references_walker(Node *node,
 										find_expr_references_context *context);
 static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
 static int	object_address_comparator(const void *a, const void *b);
-static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
-							   ObjectAddresses *addrs);
 static void add_exact_object_address_extra(const ObjectAddress *object,
 										   const ObjectAddressExtra *extra,
 										   ObjectAddresses *addrs);
@@ -2432,7 +2430,7 @@ new_object_addresses(void)
  * It is convenient to specify the class by ObjectClass rather than directly
  * by catalog OID.
  */
-static void
+void
 add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
 				   ObjectAddresses *addrs)
 {
@@ -2702,6 +2700,22 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+/*
+ * Clear an ObjectAddresses array such that it can be reused to avoid an
+ * allocation cycle.
+ */
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478bf91..ac3ac770c4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -710,68 +710,115 @@ CheckAttributeType(const char *attname,
 }
 
 /*
- * InsertPgAttributeTuple
- *		Construct and insert a new tuple in pg_attribute.
- *
- * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl and
- * attoptions are always initialized to NULL.
+ * TODO this is defined in copy.c, if we want to use this to limit the number
+ * of slots in this patch, we need to figure out where to put it.
+ */
+#define MAX_BUFFERED_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert multiple tuples in pg_attribute.
  *
- * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
- * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
- * this when inserting multiple attributes, because it's a tad more
- * expensive.)
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
  */
 void
-InsertPgAttributeTuple(Relation pg_attribute_rel,
-					   Form_pg_attribute new_attribute,
-					   CatalogIndexState indstate)
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						CatalogIndexState indstate)
 {
-	Datum		values[Natts_pg_attribute];
-	bool		nulls[Natts_pg_attribute];
-	HeapTuple	tup;
+	TupleTableSlot	  **slot;
+	TupleDesc			td;
+	int					nslots;
+	int					slotCount;
+	int					natt;
+	bool				close_index = false;
 
-	/* This is a tad tedious, but way cleaner than what we used to do... */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
+	td = RelationGetDescr(pg_attribute_rel);
 
-	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
-	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
-	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
-	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
-	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
-	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
-	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
-	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
-	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
-	values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
-	values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
-	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
-	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
-	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
-	values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
-	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
-	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
-	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
-	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
-	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
-	values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
-
-	/* start out with empty permissions and empty options */
-	nulls[Anum_pg_attribute_attacl - 1] = true;
-	nulls[Anum_pg_attribute_attoptions - 1] = true;
-	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
-	nulls[Anum_pg_attribute_attmissingval - 1] = true;
-
-	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+	/*
+	 * Allocate a set of slots in which to store the tuples to insert. In case
+	 * we have a fewer tuples than what we can allow in slots, then limit the
+	 * allocation by what we'll actually need.  This allows us to pre-make all
+	 * the slots since we know by definition that we'll be using all of them.
+	 */
+	nslots = Min(tupdesc->natts,
+				 (MAX_BUFFERED_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
 
-	/* finally insert the new tuple, update the indexes, and clean up */
-	if (indstate != NULL)
-		CatalogTupleInsertWithInfo(pg_attribute_rel, tup, indstate);
-	else
-		CatalogTupleInsert(pg_attribute_rel, tup);
+	slotCount = 0;
+	natt = 0;
 
-	heap_freetuple(tup);
+	while (natt < tupdesc->natts)
+	{
+		Form_pg_attribute	new_attributes = TupleDescAttr(tupdesc, natt);
+
+		ExecClearTuple(slot[slotCount]);
+
+		if (new_rel_oid != InvalidOid)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes->attrelid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes->attname);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes->atttypid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes->attstattarget);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes->attlen);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes->attnum);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes->attndims);
+		/* attcacheoff is always -1 in storage */
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes->atttypmod);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes->attbyval);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes->attstorage);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes->attalign);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes->attnotnull);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes->atthasdef);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes->atthasmissing);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes->attidentity);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes->attgenerated);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes->attisdropped);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes->attislocal);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes->attinhcount);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes->attcollation);
+
+		/* start out with empty permissions and empty options */
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/*
+		 * If we've exhausted all the slots for the batch, or we know that
+		 * there are no more tuples, store them in the catalog.
+		 */
+		if (slotCount == nslots || natt == tupdesc->natts - 1)
+		{
+			if (!indstate)
+			{
+				indstate = CatalogOpenIndexes(pg_attribute_rel);
+				close_index = true;
+			}
+
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
+
+		natt++;
+	}
+
+	if (close_index)
+		CatalogCloseIndexes(indstate);
+
+	for (int i = 0; i < Min(tupdesc->natts, nslots); i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /* --------------------------------
@@ -786,8 +833,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -801,35 +846,28 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, indstate);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * Now add dependencies on their datatypes and collations.
 	 */
-	for (i = 0; i < natts; i++)
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, indstate);
-
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = tupdesc->attrs[i].atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = tupdesc->attrs[i].attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -842,17 +880,10 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
-
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
+		TupleDesc td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
@@ -3540,6 +3571,7 @@ StorePartitionKey(Relation rel,
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3587,27 +3619,25 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
-
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_OPCLASS, partopclass[i], 0, refobjs);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
-
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_COLLATION, partcollation[i], 0, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * The partitioning columns are made internally dependent on the table,
 	 * because we cannot drop any of them without dropping the whole table.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586c37..66c6ecac74 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -105,7 +105,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, int numatts);
+static void AppendAttributeTuples(Relation indexRelation);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -484,12 +484,11 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, int numatts)
+AppendAttributeTuples(Relation indexRelation)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
 	TupleDesc	indexTupDesc;
-	int			i;
 
 	/*
 	 * open the attribute relation and its indexes
@@ -503,14 +502,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -976,7 +968,7 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs);
+	AppendAttributeTuples(indexRelation);
 
 	/* ----------------
 	 *	  update pg_index
@@ -1025,11 +1017,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1068,12 +1063,9 @@ index_create(Relation heapRelation,
 			{
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					add_object_address(OCLASS_CLASS, heapRelationId,
+									   indexInfo->ii_IndexAttrNumbers[i],
+									   refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1092,6 +1084,8 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
 		}
 
 		/*
@@ -1115,30 +1109,25 @@ index_create(Relation heapRelation,
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 		}
 
+
 		/* Store dependency on collations */
 		/* The default collation is pinned, so don't bother recording it */
+		reset_object_addresses(refobjs);
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				add_object_address(OCLASS_COLLATION, collationObjectId[i], 0,
+								   refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
-		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			add_object_address(OCLASS_OPCLASS, classObjectId[i], 0, refobjs);
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
@@ -1159,6 +1148,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index d63fcf58cf..b98d4bffbd 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 0b7face4cc..00b0170914 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -104,8 +104,8 @@ AggregateCreate(const char *aggName,
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -740,83 +740,44 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_PROC, transfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, finalfn, 0, refobjs);
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, combinefn, 0, refobjs);
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, serialfn, 0, refobjs);
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, deserialfn, 0, refobjs);
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mtransfn, 0, refobjs);
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, minvtransfn, 0, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, mfinalfn, 0, refobjs);
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
-	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_OPERATOR, sortop, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
 
 	return myself;
 }
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 3d2b1cc911..334f3d7948 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -91,6 +91,7 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -229,30 +230,22 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
-			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
-			}
+				add_object_address(OCLASS_CLASS, relId, constraintKey[i], refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			add_object_address(OCLASS_CLASS, relId, 0, refobjs);
 		}
 	}
 
@@ -261,14 +254,13 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
+		add_object_address(OCLASS_TYPE, domainId, 0, refobjs);
+	}
 
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
 
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
-	}
+	reset_object_addresses(refobjs);
 
 	if (OidIsValid(foreignRelId))
 	{
@@ -276,24 +268,15 @@ CreateConstraintEntry(const char *constraintName,
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
-			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_CLASS, foreignRelId, foreignKey[i],
+								   refobjs);
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_CLASS, foreignRelId, 0, refobjs);
 		}
 	}
 
@@ -305,13 +288,7 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_CLASS, indexRelId, 0, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -322,28 +299,22 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			add_object_address(OCLASS_OPERATOR, pfEqOp[i], 0, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ppEqOp[i], 0, refobjs);
+
 			if (ffEqOp[i] != pfEqOp[i])
-			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
-			}
+				add_object_address(OCLASS_OPERATOR, ffEqOp[i], 0, refobjs);
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index f9af245eec..216be7bea1 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,10 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
-	bool		nulls[Natts_pg_depend];
-	Datum		values[Natts_pg_depend];
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -79,9 +123,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -90,34 +135,47 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		 */
 		if (!isObjectPinned(referenced, dependDesc))
 		{
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+			ExecClearTuple(slot[ntuples]);
 			/*
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+			slot[ntuples]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
-			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
-			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
 
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+			memset(slot[ntuples]->tts_isnull, false,
+				   slot[ntuples]->tts_tupleDescriptor->natts * sizeof(bool));
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreVirtualTuple(slot[ntuples]);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
+
+	for (int i = 0; i < ntuples; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 340e284ae4..f1f985f8bc 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,15 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -792,39 +794,19 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
-	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_SCHEMA, oper->oprnamespace, 0, refobjs);
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprleft, 0, refobjs);
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprright, 0, refobjs);
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
-	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TYPE, oper->oprresult, 0, refobjs);
 
 	/*
 	 * NOTE: we do not consider the operator to depend on the associated
@@ -837,30 +819,18 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprcode, 0, refobjs);
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprrest, 0, refobjs);
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, oper->oprjoin, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 5194dcaac0..4e3927af4b 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -114,8 +114,8 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
 	int			i;
 	Oid			trfid;
 
@@ -611,49 +611,29 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, procNamespace, 0, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_LANGUAGE, languageObjectId, 0, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_TYPE, returnType, 0, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
-	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_TYPE, allParams[i], 0, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
-		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+			add_object_address(OCLASS_TRANSFORM, trfid, 0, refobjs);
 	}
 
 	/* dependency on parameter default expressions */
@@ -663,12 +643,10 @@ ProcedureCreate(const char *procedureName,
 
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
+		add_object_address(OCLASS_PROC, prosupport, 0, refobjs);
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
 
 	/* dependency on owner */
 	if (!is_update)
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 2ef792dbd7..1ab69c3640 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -799,10 +799,15 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	HeapTuple	newtuple;
+	int			ntuples;
 	CatalogIndexState indstate;
 	Datum		values[Natts_pg_shdepend];
 	bool		nulls[Natts_pg_shdepend];
 	bool		replace[Natts_pg_shdepend];
+	/* TODO figure out a sensible value for the amount of slots */
+#define	DEPEND_TUPLE_BUF 32
+	TupleTableSlot *slot[DEPEND_TUPLE_BUF];
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
@@ -833,16 +838,29 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	ntuples = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
-
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
-
-		heap_freetuple(newtup);
+		/* Dont create if already created */
+		if (!slot[ntuples])
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(sdepRel),
+													 &TTSOpsHeapTuple);
+		ExecClearTuple(slot[ntuples]);
+		newtuple = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
+		ExecStoreHeapTuple(newtuple, slot[ntuples], false);
+		ntuples++;
+
+		if (ntuples == DEPEND_TUPLE_BUF)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+			ntuples = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (ntuples)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 8d7572da51..5dd2b67c5f 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -544,7 +544,9 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool rebuild)
 {
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
@@ -579,63 +581,54 @@ GenerateTypeDependencies(Oid typeObjectId,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index a0db7db411..0295f1a29f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1782,7 +1782,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	bool		nulls[Natts_pg_extension];
 	HeapTuple	tuple;
 	ObjectAddress myself;
-	ObjectAddress nsp;
+	ObjectAddresses *refobjs;
 	ListCell   *lc;
 
 	/*
@@ -1823,29 +1823,26 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	/*
 	 * Record dependencies on owner, schema, and prerequisite extensions
 	 */
+	refobjs = new_object_addresses();
+
 	recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
 
 	myself.classId = ExtensionRelationId;
 	myself.objectId = extensionOid;
 	myself.objectSubId = 0;
 
-	nsp.classId = NamespaceRelationId;
-	nsp.objectId = schemaOid;
-	nsp.objectSubId = 0;
-
-	recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+	add_object_address(OCLASS_SCHEMA, schemaOid, 0, refobjs);
 
 	foreach(lc, requiredExtensions)
 	{
 		Oid			reqext = lfirst_oid(lc);
-		ObjectAddress otherext;
-
-		otherext.classId = ExtensionRelationId;
-		otherext.objectId = reqext;
-		otherext.objectSubId = 0;
 
-		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+		add_object_address(OCLASS_EXTENSION, reqext, 0, refobjs);
 	}
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7c8d663fc..386f8a74c2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5925,6 +5925,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	AlterTableCmd *childcmd;
 	AclResult	aclresult;
 	ObjectAddress address;
+	MemoryContext oldcontext;
+	TupleDesc	tupdesc;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -6058,6 +6060,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					   0);
 
 	/* construct new attribute's pg_attribute entry */
+	oldcontext = MemoryContextSwitchTo(CurTransactionContext);
 	attribute.attrelid = myrelid;
 	namestrcpy(&(attribute.attname), colDef->colname);
 	attribute.atttypid = typeOid;
@@ -6082,10 +6085,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	ReleaseSysCache(typeTuple);
 
-	InsertPgAttributeTuple(attrdesc, &attribute, NULL);
+	FormData_pg_attribute *aattr[] = {&attribute};
+	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
+	MemoryContextSwitchTo(oldcontext);
+
 	/*
 	 * Update pg_class tuple as appropriate
 	 */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 0cd6fcf027..0f8084812d 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -162,6 +162,9 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
 
 extern ObjectAddresses *new_object_addresses(void);
 
+extern void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
+							   ObjectAddresses *addrs);
+
 extern void add_exact_object_address(const ObjectAddress *object,
 									 ObjectAddresses *addrs);
 
@@ -174,6 +177,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index bd64024946..5271489a6e 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -93,10 +93,10 @@ extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
 extern List *heap_truncate_find_FKs(List *relationIds);
 
-extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
-								   Form_pg_attribute new_attribute,
-								   CatalogIndexState indstate);
-
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									CatalogIndexState indstate);
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
 							   Oid new_rel_oid,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..a7e2a9b26b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 6ddf3a63c3..f08ac5efa5 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2069,11 +2069,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2097,11 +2096,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
-- 
2.21.1 (Apple Git-122.3)

decodemultiinsert_tupledata.patchapplication/octet-stream; name=decodemultiinsert_tupledata.patch; x-unix-mode=0644Download
From e3e7a720d7325a477e6d189d34619f05bdf568df Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Tue, 25 Feb 2020 22:21:40 +0100
Subject: [PATCH 2/2] Fix DecodeMultiInsert to handle catalog multi inserts

The coding in DecodeMultiInsert assumed that multi inserts weren't used
for catalogs, and would fail on tupledata being NULL in case they were.
This is currently safe as there are no multi inserts for the catalog,
but that might change leading to this codepath falling over.
---
 src/backend/replication/logical/decode.c | 63 +++++++++++-------------
 1 file changed, 30 insertions(+), 33 deletions(-)

diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 5e1dc8a651..140d9a0710 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -891,6 +891,14 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
 
+	/*
+	 * CONTAINS_NEW_TUPLE will always be set unless the multi_insert was
+	 * performed for a catalog.  If it is a catalog, return immediately as
+	 * there is nothing to logically decode.
+	 */
+	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
+		return;
+
 	/* only interested in our database */
 	XLogRecGetBlockTag(r, 0, &rnode, NULL, NULL);
 	if (rnode.dbNode != ctx->slot->data.database)
@@ -901,8 +909,8 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		return;
 
 	/*
-	 * As multi_insert is not used for catalogs yet, the block should always
-	 * have data even if a full-page write of it is taken.
+	 * We know that this multi_insert isn't for a catalog, so the block should
+	 * always have data even if a full-page write of it is taken.
 	 */
 	tupledata = XLogRecGetBlockData(r, 0, &tuplelen);
 	Assert(tupledata != NULL);
@@ -914,6 +922,7 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		xl_multi_insert_tuple *xlhdr;
 		int			datalen;
 		ReorderBufferTupleBuf *tuple;
+		HeapTupleHeader header;
 
 		change = ReorderBufferGetChange(ctx->reorder);
 		change->action = REORDER_BUFFER_CHANGE_INSERT;
@@ -925,43 +934,31 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		data = ((char *) xlhdr) + SizeOfMultiInsertTuple;
 		datalen = xlhdr->datalen;
 
-		/*
-		 * CONTAINS_NEW_TUPLE will always be set currently as multi_insert
-		 * isn't used for catalogs, but better be future proof.
-		 *
-		 * We decode the tuple in pretty much the same way as DecodeXLogTuple,
-		 * but since the layout is slightly different, we can't use it here.
-		 */
-		if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)
-		{
-			HeapTupleHeader header;
-
-			change->data.tp.newtuple =
-				ReorderBufferGetTupleBuf(ctx->reorder, datalen);
+		change->data.tp.newtuple =
+			ReorderBufferGetTupleBuf(ctx->reorder, datalen);
 
-			tuple = change->data.tp.newtuple;
-			header = tuple->tuple.t_data;
+		tuple = change->data.tp.newtuple;
+		header = tuple->tuple.t_data;
 
-			/* not a disk based tuple */
-			ItemPointerSetInvalid(&tuple->tuple.t_self);
+		/* not a disk based tuple */
+		ItemPointerSetInvalid(&tuple->tuple.t_self);
 
-			/*
-			 * We can only figure this out after reassembling the
-			 * transactions.
-			 */
-			tuple->tuple.t_tableOid = InvalidOid;
+		/*
+		 * We can only figure this out after reassembling the
+		 * transactions.
+		 */
+		tuple->tuple.t_tableOid = InvalidOid;
 
-			tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
+		tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
 
-			memset(header, 0, SizeofHeapTupleHeader);
+		memset(header, 0, SizeofHeapTupleHeader);
 
-			memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
-				   (char *) data,
-				   datalen);
-			header->t_infomask = xlhdr->t_infomask;
-			header->t_infomask2 = xlhdr->t_infomask2;
-			header->t_hoff = xlhdr->t_hoff;
-		}
+		memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
+			   (char *) data,
+			   datalen);
+		header->t_infomask = xlhdr->t_infomask;
+		header->t_infomask2 = xlhdr->t_infomask2;
+		header->t_hoff = xlhdr->t_hoff;
 
 		/*
 		 * Reset toast reassembly state only after the last row in the last
-- 
2.21.1 (Apple Git-122.3)

#31Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#30)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Tue, Feb 25, 2020 at 10:44:40PM +0100, Daniel Gustafsson wrote:

In doing that I realized that there is another hunk in this patch for fixing up
logical decoding multi-insert support, which is independent of the patch in
question here so I split it off. It's another issue which cause no harm at all
today, but fails as soon as someone tries to perform multi inserts into the
catalog.

Yep. I was wondering how we should do that for some time, but I was
not able to come back to it.

+   /*
+    * CONTAINS_NEW_TUPLE will always be set unless the multi_insert was
+    * performed for a catalog.  If it is a catalog, return immediately as
+    * there is nothing to logically decode.
+    */
+   if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
+       return;
Hmm, OK.  Consistency with DecodeInsert() is a good idea here, so
count me in regarding the way your patch handles the problem.  I would
be fine to apply that part but, Andres, perhaps you would prefer
taking care of it yourself?
--
Michael
#32Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#31)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Fri, Feb 28, 2020 at 05:24:29PM +0900, Michael Paquier wrote:

+   /*
+    * CONTAINS_NEW_TUPLE will always be set unless the multi_insert was
+    * performed for a catalog.  If it is a catalog, return immediately as
+    * there is nothing to logically decode.
+    */
+   if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
+       return;
Hmm, OK.  Consistency with DecodeInsert() is a good idea here, so
count me in regarding the way your patch handles the problem.  I would
be fine to apply that part but, Andres, perhaps you would prefer
taking care of it yourself?

Okay, I have applied this part.

One comment removal is missed in heapam.c (my fault, then).

+ * TODO this is defined in copy.c, if we want to use this to limit the number
+ * of slots in this patch, we need to figure out where to put it.
+ */
+#define MAX_BUFFERED_BYTES 65535
The use-case is different than copy.c, so aren't you looking at a
separate variable to handle that?
+reset_object_addresses(ObjectAddresses *addrs)
+{
+   if (addrs->numrefs == 0)
+       return;
Or just use an assert?

src/backend/commands/tablecmds.c: /* attribute.attacl is handled by
InsertPgAttributeTuple */
src/backend/catalog/heap.c: * This is a variant of
InsertPgAttributeTuple() which dynamically allocates
Those two references are not true anymore as your patch removes
InsertPgAttributeTuple() and replaces it by the plural flavor.

+/*
+ * InsertPgAttributeTuples
+ *     Construct and insert multiple tuples in pg_attribute.
  *
- * Caller has already opened and locked pg_attribute. new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl and
- * attoptions are always initialized to NULL.
- *
- * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
- * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
- * this when inserting multiple attributes, because it's a tad more
- * expensive.)
+ * This is a variant of InsertPgAttributeTuple() which dynamically allocates
+ * space for multiple tuples. Having two so similar functions is a kludge, but
+ * for now it's a TODO to make it less terrible.
And those comments largely need to remain around.

- attr = TupleDescAttr(tupdesc, i);
- /* Fill in the correct relation OID */
- attr->attrelid = new_rel_oid;
- /* Make sure this is OK, too */
- attr->attstattarget = -1;
-
- InsertPgAttributeTuple(rel, attr, indstate);
attstattarget handling is inconsistent here, no?
InsertPgAttributeTuples() does not touch this part, though your code
updates the attribute's attrelid.

-    referenced.classId = RelationRelationId;
-    referenced.objectId = heapRelationId;
-    referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-    recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+    add_object_address(OCLASS_CLASS, heapRelationId,
+                       indexInfo->ii_IndexAttrNumbers[i],
+                       refobjs);
Not convinced that we have to publish add_object_address() in the
headers, because we have already add_exact_object_address which is
able to do the same job.  All code paths touched by your patch involve
already ObjectAddress objects, so switching to _exact_ creates less
code diffs.
+       if (ntuples == DEPEND_TUPLE_BUF)
+       {
+           CatalogTuplesMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+           ntuples = 0;
+       }
Maybe this is a sufficient argument that we had better consider the
template copy as part of a separate patch, handled once the basics is
in place.  It is not really obvious if 32 is a good thing, or not.
+   /* TODO is nreferenced a reasonable allocation of slots? */
+   slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
I guess so.
    /* construct new attribute's pg_attribute entry */
+   oldcontext = MemoryContextSwitchTo(CurTransactionContext);
This needs a comment as this change is not obvious for the reader.
And actually, why?  
--
Michael
#33Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#32)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 2 Mar 2020, at 03:06, Michael Paquier <michael@paquier.xyz> wrote:

Thanks a lot for another round of review, much appreciated!

On Fri, Feb 28, 2020 at 05:24:29PM +0900, Michael Paquier wrote:

+   /*
+    * CONTAINS_NEW_TUPLE will always be set unless the multi_insert was
+    * performed for a catalog.  If it is a catalog, return immediately as
+    * there is nothing to logically decode.
+    */
+   if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
+       return;
Hmm, OK.  Consistency with DecodeInsert() is a good idea here, so
count me in regarding the way your patch handles the problem.  I would
be fine to apply that part but, Andres, perhaps you would prefer
taking care of it yourself?

Okay, I have applied this part.

Thanks!

+#define MAX_BUFFERED_BYTES 65535
The use-case is different than copy.c, so aren't you looking at a
separate variable to handle that?

Renamed to indicate current usecase.

+reset_object_addresses(ObjectAddresses *addrs)
+{
+   if (addrs->numrefs == 0)
+       return;
Or just use an assert?

I don't see why we should prohibit calling reset_object_addresses so strongly,
it's nonsensical but is it wrong enough to Assert on? I can change it if you
feel it should be an assertion, but have left it for now.

src/backend/commands/tablecmds.c: /* attribute.attacl is handled by
InsertPgAttributeTuple */
src/backend/catalog/heap.c: * This is a variant of
InsertPgAttributeTuple() which dynamically allocates
Those two references are not true anymore as your patch removes
InsertPgAttributeTuple() and replaces it by the plural flavor.

Fixed.

+/*
+ * InsertPgAttributeTuples
...
And those comments largely need to remain around.

Fixed.

attstattarget handling is inconsistent here, no?

Indeed it was, fixed.

Not convinced that we have to publish add_object_address() in the
headers, because we have already add_exact_object_address which is
able to do the same job. All code paths touched by your patch involve
already ObjectAddress objects, so switching to _exact_ creates less
code diffs.

Good point, using _exact will make the changes smaller at the cost of additions
being larger. I do prefer the add_object_address API since it makes code more
readable IMO, but there is clear value in not exposing more functions. I've
changed to using add_exact_object_address in the attached version.

+       if (ntuples == DEPEND_TUPLE_BUF)
+       {
+           CatalogTuplesMultiInsertWithInfo(sdepRel, slot, ntuples, indstate);
+           ntuples = 0;
+       }
Maybe this is a sufficient argument that we had better consider the
template copy as part of a separate patch, handled once the basics is
in place.  It is not really obvious if 32 is a good thing, or not.

Fixed by instead avoiding the copying altogether and creating virtual tuples.
Andres commented on this upthread but I had missed fixing the code to do that,
so better late than never.

/* construct new attribute's pg_attribute entry */
+ oldcontext = MemoryContextSwitchTo(CurTransactionContext);
This needs a comment as this change is not obvious for the reader.
And actually, why?

Thats a good question, this was a leftover from a different version the code
which I abandoned, but I missed removing the context handling. Sorry about the
sloppyness there. Removed.

The v9 patch attached addresses, I believe, all comments made to date. I tried
to read through earlier reviews too to ensure I hadn't missed anything so I
hope I've covered all findings so far.

cheers ./daniel

Attachments:

catalog_multi_insert-v9.patchapplication/octet-stream; name=catalog_multi_insert-v9.patch; x-unix-mode=0644Download
From cac71abc55c613cac24e351054d22452067a284d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Tue, 12 Nov 2019 15:14:10 +0100
Subject: [PATCH] WIP: Use heap_multi_insert for catalog relations, v9

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once, and replaces
InsertPgAttributeTuple.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/access/heap/heapam.c           |   4 -
 src/backend/catalog/dependency.c           |  16 ++
 src/backend/catalog/heap.c                 | 216 ++++++++++++---------
 src/backend/catalog/index.c                |  55 +++---
 src/backend/catalog/indexing.c             |  36 ++++
 src/backend/catalog/pg_aggregate.c         |  63 +++---
 src/backend/catalog/pg_constraint.c        |  76 +++-----
 src/backend/catalog/pg_depend.c            |  96 +++++++--
 src/backend/catalog/pg_operator.c          |  52 +++--
 src/backend/catalog/pg_proc.c              |  52 +++--
 src/backend/catalog/pg_shdepend.c          |  52 +++--
 src/backend/catalog/pg_type.c              |  51 +++--
 src/backend/commands/extension.c           |  25 +--
 src/backend/commands/tablecmds.c           |   8 +-
 src/include/catalog/dependency.h           |   2 +
 src/include/catalog/heap.h                 |   8 +-
 src/include/catalog/indexing.h             |   5 +
 src/test/regress/expected/create_index.out |   6 +-
 18 files changed, 478 insertions(+), 345 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 5a32e62ed0..fbbf238ecf 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2208,10 +2208,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			RelationPutHeapTuple(relation, buffer, heaptup, false);
 
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
 			if (needwal && need_cids)
 				log_heap_new_cid(relation, heaptup);
 		}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index c4a4df25b8..4429b82d8b 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2702,6 +2702,22 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+/*
+ * Clear an ObjectAddresses array such that it can be reused to avoid an
+ * allocation cycle.
+ */
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915979..1d56951ecc 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -710,68 +710,118 @@ CheckAttributeType(const char *attname,
 }
 
 /*
- * InsertPgAttributeTuple
- *		Construct and insert a new tuple in pg_attribute.
+ * Cap the maximum amount of bytes allocated for InsertPgAttributeTuples slots
+ */
+#define MAX_ATTRINSERT_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert tuple(s) in pg_attribute.
  *
- * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl and
+ * Caller has already opened and locked pg_attribute. tupdesc describes the
+ * attributes to insert.  attcacheoff is always initialized to -1, attacl and
  * attoptions are always initialized to NULL.
  *
- * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
- * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
- * this when inserting multiple attributes, because it's a tad more
- * expensive.)
+ * If the catalog index passed in indstate is NULL, it will be opened before
+ * insertion and subsequently closed. If the index is opened and passed in
+ * the indstate, it's left open when leaving this function.
  */
 void
-InsertPgAttributeTuple(Relation pg_attribute_rel,
-					   Form_pg_attribute new_attribute,
-					   CatalogIndexState indstate)
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						CatalogIndexState indstate)
 {
-	Datum		values[Natts_pg_attribute];
-	bool		nulls[Natts_pg_attribute];
-	HeapTuple	tup;
+	TupleTableSlot	  **slot;
+	TupleDesc			td;
+	int					nslots;
+	int					slotCount;
+	int					natt;
+	bool				close_index = false;
 
-	/* This is a tad tedious, but way cleaner than what we used to do... */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
+	td = RelationGetDescr(pg_attribute_rel);
 
-	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
-	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
-	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
-	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
-	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
-	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
-	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
-	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
-	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
-	values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
-	values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
-	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
-	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
-	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
-	values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
-	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
-	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
-	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
-	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
-	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
-	values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
-
-	/* start out with empty permissions and empty options */
-	nulls[Anum_pg_attribute_attacl - 1] = true;
-	nulls[Anum_pg_attribute_attoptions - 1] = true;
-	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
-	nulls[Anum_pg_attribute_attmissingval - 1] = true;
-
-	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+	/*
+	 * Allocate a set of slots in which to store the tuples to insert. In case
+	 * we have a fewer tuples than what we can allow in slots, then limit the
+	 * allocation by what we'll actually need.  This allows us to pre-make all
+	 * the slots since we know by definition that we'll be using all of them.
+	 */
+	nslots = Min(tupdesc->natts,
+				 (MAX_ATTRINSERT_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
 
-	/* finally insert the new tuple, update the indexes, and clean up */
-	if (indstate != NULL)
-		CatalogTupleInsertWithInfo(pg_attribute_rel, tup, indstate);
-	else
-		CatalogTupleInsert(pg_attribute_rel, tup);
+	slotCount = 0;
+	natt = 0;
 
-	heap_freetuple(tup);
+	while (natt < tupdesc->natts)
+	{
+		Form_pg_attribute	new_attributes = TupleDescAttr(tupdesc, natt);
+
+		ExecClearTuple(slot[slotCount]);
+
+		if (new_rel_oid != InvalidOid)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attributes->attrelid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attributes->attname);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attributes->atttypid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attributes->attstattarget);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attributes->attlen);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attributes->attnum);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attributes->attndims);
+		/* attcacheoff is always -1 in storage */
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attributes->atttypmod);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attributes->attbyval);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attributes->attstorage);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attributes->attalign);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attributes->attnotnull);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attributes->atthasdef);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attributes->atthasmissing);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attributes->attidentity);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attributes->attgenerated);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attributes->attisdropped);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attributes->attislocal);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attributes->attinhcount);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attributes->attcollation);
+
+		/* start out with empty permissions and empty options */
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/*
+		 * If we've exhausted all the slots for the batch, or we know that
+		 * there are no more tuples, store them in the catalog.
+		 */
+		if (slotCount == nslots || natt == tupdesc->natts - 1)
+		{
+			if (!indstate)
+			{
+				indstate = CatalogOpenIndexes(pg_attribute_rel);
+				close_index = true;
+			}
+
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
+
+		natt++;
+	}
+
+	if (close_index)
+		CatalogCloseIndexes(indstate);
+
+	for (int i = 0; i < Min(tupdesc->natts, nslots); i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /* --------------------------------
@@ -786,8 +836,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -801,35 +849,30 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	for (int i = 0; i < natts; i++)
+		tupdesc->attrs[i].attstattarget = -1;
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, indstate);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * Now add dependencies on their datatypes and collations.
 	 */
-	for (i = 0; i < natts; i++)
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, indstate);
-
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = tupdesc->attrs[i].atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = tupdesc->attrs[i].attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -842,17 +885,10 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
+		TupleDesc td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
-
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
@@ -3540,6 +3576,7 @@ StorePartitionKey(Relation rel,
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3587,27 +3624,32 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
+		ObjectAddress	ocobj;
+
+		ObjectAddressSet(ocobj, OperatorClassRelationId, partopclass[i]);
+		add_exact_object_address(&ocobj, refobjs);
 
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
+			ObjectAddress	collobj;
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSet(collobj, CollationRelationId, partcollation[i]);
+			add_exact_object_address(&collobj, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * The partitioning columns are made internally dependent on the table,
 	 * because we cannot drop any of them without dropping the whole table.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1681f61727..a363af4565 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -105,7 +105,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, int numatts);
+static void AppendAttributeTuples(Relation indexRelation);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -484,12 +484,11 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, int numatts)
+AppendAttributeTuples(Relation indexRelation)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
 	TupleDesc	indexTupDesc;
-	int			i;
 
 	/*
 	 * open the attribute relation and its indexes
@@ -503,14 +502,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -976,7 +968,7 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs);
+	AppendAttributeTuples(indexRelation);
 
 	/* ----------------
 	 *	  update pg_index
@@ -1025,11 +1017,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1066,14 +1061,14 @@ index_create(Relation heapRelation,
 			/* Create auto dependencies on simply-referenced columns */
 			for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 			{
+				ObjectAddress obj;
+
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					ObjectAddressSubSet(obj, RelationRelationId,
+										heapRelationId,
+										indexInfo->ii_IndexAttrNumbers[i]);
+					add_exact_object_address(&obj, refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1092,6 +1087,8 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
 		}
 
 		/*
@@ -1115,31 +1112,33 @@ index_create(Relation heapRelation,
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 		}
 
+
 		/* Store dependency on collations */
 		/* The default collation is pinned, so don't bother recording it */
+		reset_object_addresses(refobjs);
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
+			ObjectAddress	obj;
+
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				ObjectAddressSet(obj, CollationRelationId, collationObjectId[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			ObjectAddress	obj;
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSet(obj, OperatorClassRelationId, classObjectId[i]);
+			add_exact_object_address(&obj, refobjs);
 		}
 
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
 		{
@@ -1159,6 +1158,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index d63fcf58cf..b98d4bffbd 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 0b7face4cc..be8791f520 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -104,8 +104,9 @@ AggregateCreate(const char *aggName,
 	Oid			procOid;
 	TupleDesc	tupDesc;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -740,84 +741,70 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSet(obj, ProcedureRelationId, transfn);
+	add_exact_object_address(&obj, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, finalfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, combinefn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, serialfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, deserialfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, mtransfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, minvtransfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, mfinalfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
 	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, OperatorRelationId, sortop, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+
 	return myself;
 }
 
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 90932be831..cea63a6f4a 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -91,6 +91,8 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -229,30 +231,26 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
 			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+				ObjectAddressSubSet(obj, RelationRelationId, relId, constraintKey[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			ObjectAddressSubSet(obj, RelationRelationId, relId, 0);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -261,14 +259,14 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
+		ObjectAddressSubSet(obj, TypeRelationId, domainId, 0);
+		add_exact_object_address(&obj, refobjs);
+	}
 
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
 
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
-	}
+	reset_object_addresses(refobjs);
 
 	if (OidIsValid(foreignRelId))
 	{
@@ -276,24 +274,18 @@ CreateConstraintEntry(const char *constraintName,
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
 			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+				ObjectAddressSubSet(obj, RelationRelationId, foreignRelId, foreignKey[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			ObjectAddressSubSet(obj, RelationRelationId, foreignRelId, 0);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -305,13 +297,8 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, RelationRelationId, indexRelId, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -322,28 +309,29 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			ObjectAddressSubSet(obj, OperatorRelationId, pfEqOp[i], 0);
+			add_exact_object_address(&obj, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
 			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+				ObjectAddressSubSet(obj, OperatorRelationId, ppEqOp[i], 0);
+				add_exact_object_address(&obj, refobjs);
 			}
+
 			if (ffEqOp[i] != pfEqOp[i])
 			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+				ObjectAddressSubSet(obj, OperatorRelationId, ffEqOp[i], 0);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index f9af245eec..216be7bea1 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,10 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
-	bool		nulls[Natts_pg_depend];
-	Datum		values[Natts_pg_depend];
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -79,9 +123,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -90,34 +135,47 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		 */
 		if (!isObjectPinned(referenced, dependDesc))
 		{
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+			ExecClearTuple(slot[ntuples]);
 			/*
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+			slot[ntuples]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
-			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
-			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
 
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+			memset(slot[ntuples]->tts_isnull, false,
+				   slot[ntuples]->tts_tupleDescriptor->natts * sizeof(bool));
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreVirtualTuple(slot[ntuples]);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
+
+	for (int i = 0; i < ntuples; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 340e284ae4..4e80438650 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,16 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -793,37 +796,29 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
 	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, NamespaceRelationId, oper->oprnamespace, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TypeRelationId, oper->oprleft, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TypeRelationId, oper->oprright, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TypeRelationId, oper->oprresult, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/*
@@ -838,30 +833,27 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, oper->oprcode, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, oper->oprrest, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, oper->oprjoin, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
 							oper->oprowner);
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 423fd79d94..3540d7bbc3 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -114,8 +114,9 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 	int			i;
 	Oid			trfid;
 
@@ -611,48 +612,38 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSubSet(obj, NamespaceRelationId, procNamespace, 0);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSubSet(obj, LanguageRelationId, languageObjectId, 0);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSubSet(obj, TypeRelationId, returnType, 0);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
 	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TransformRelationId, trfid, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TypeRelationId, allParams[i], 0);
+		add_exact_object_address(&obj, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
 		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSubSet(obj, TransformRelationId, trfid, 0);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -664,12 +655,13 @@ ProcedureCreate(const char *procedureName,
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, prosupport, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* dependency on owner */
 	if (!is_update)
 		recordDependencyOnOwner(ProcedureRelationId, retval, proowner);
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 2ef792dbd7..b84020543a 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -785,6 +785,8 @@ checkSharedDependencies(Oid classId, Oid objectId,
 	return true;
 }
 
+#define MAX_TEMPLATE_BYTES 65535
+
 /*
  * copyTemplateDependencies
  *
@@ -799,14 +801,19 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	int			slotCount;
 	CatalogIndexState indstate;
-	Datum		values[Natts_pg_shdepend];
-	bool		nulls[Natts_pg_shdepend];
-	bool		replace[Natts_pg_shdepend];
+	TupleTableSlot **slot;
+	int			nslots;
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
 
+	nslots = MAX_TEMPLATE_BYTES / sizeof(FormData_pg_shdepend);
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+
 	indstate = CatalogOpenIndexes(sdepRel);
 
 	/* Scan all entries with dbid = templateDbId */
@@ -818,14 +825,6 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
 							  NULL, 1, key);
 
-	/* Set up to copy the tuples except for inserting newDbId */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
-	memset(replace, false, sizeof(replace));
-
-	replace[Anum_pg_shdepend_dbid - 1] = true;
-	values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(newDbId);
-
 	/*
 	 * Copy the entries of the original database, changing the database Id to
 	 * that of the new database.  Note that because we are not copying rows
@@ -833,20 +832,43 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	slotCount = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
+		ExecClearTuple(slot[slotCount]);
 
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		Form_pg_shdepend shdep = (Form_pg_shdepend) GETSTRUCT(tup);
 
-		heap_freetuple(newtup);
+		slot[slotCount]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId);
+		slot[slotCount]->tts_values[Anum_pg_shdepend_classid] = shdep->classid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objid] = shdep->objid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		if (slotCount == nslots)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (slotCount)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
 	table_close(sdepRel, RowExclusiveLock);
+
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /*
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 56e0bcf39e..4ffea7d6ea 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -545,7 +545,9 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool rebuild)
 {
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
@@ -580,63 +582,54 @@ GenerateTypeDependencies(Oid typeObjectId,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 00cf4ef268..834da41d21 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1782,7 +1782,8 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	bool		nulls[Natts_pg_extension];
 	HeapTuple	tuple;
 	ObjectAddress myself;
-	ObjectAddress nsp;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 	ListCell   *lc;
 
 	/*
@@ -1823,29 +1824,29 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	/*
 	 * Record dependencies on owner, schema, and prerequisite extensions
 	 */
+	refobjs = new_object_addresses();
+
 	recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
 
 	myself.classId = ExtensionRelationId;
 	myself.objectId = extensionOid;
 	myself.objectSubId = 0;
 
-	nsp.classId = NamespaceRelationId;
-	nsp.objectId = schemaOid;
-	nsp.objectSubId = 0;
-
-	recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+	ObjectAddressSubSet(obj, NamespaceRelationId, schemaOid, 0);
+	add_exact_object_address(&obj, refobjs);
 
 	foreach(lc, requiredExtensions)
 	{
 		Oid			reqext = lfirst_oid(lc);
-		ObjectAddress otherext;
-
-		otherext.classId = ExtensionRelationId;
-		otherext.objectId = reqext;
-		otherext.objectSubId = 0;
+		ObjectAddress reqobj;
 
-		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(reqobj, ExtensionRelationId, reqext, 0);
+		add_exact_object_address(&reqobj, refobjs);
 	}
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a13b97164..6f46fb97cd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5925,6 +5925,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	AlterTableCmd *childcmd;
 	AclResult	aclresult;
 	ObjectAddress address;
+	TupleDesc	tupdesc;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -6078,11 +6079,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
-	/* attribute.attacl is handled by InsertPgAttributeTuple */
+	/* attribute.attacl is handled by InsertPgAttributeTuples */
 
 	ReleaseSysCache(typeTuple);
 
-	InsertPgAttributeTuple(attrdesc, &attribute, NULL);
+	FormData_pg_attribute *aattr[] = {&attribute};
+	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 0cd6fcf027..5ae1f1d1ba 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -174,6 +174,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index bd64024946..5271489a6e 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -93,10 +93,10 @@ extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
 extern List *heap_truncate_find_FKs(List *relationIds);
 
-extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
-								   Form_pg_attribute new_attribute,
-								   CatalogIndexState indstate);
-
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									CatalogIndexState indstate);
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
 							   Oid new_rel_oid,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..a7e2a9b26b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index ae95bb38a6..3e90565da5 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2069,11 +2069,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2097,11 +2096,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
-- 
2.21.1 (Apple Git-122.3)

#34Daniel Gustafsson
daniel@yesql.se
In reply to: Daniel Gustafsson (#33)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 4 Mar 2020, at 23:16, Daniel Gustafsson <daniel@yesql.se> wrote:

The v9 patch attached addresses, I believe, all comments made to date. I tried
to read through earlier reviews too to ensure I hadn't missed anything so I
hope I've covered all findings so far.

Attached is a rebased version which was updated to handle the changes for op
class parameters introduced in 911e70207703799605.

cheers ./daniel

Attachments:

catalog_multi_insert-v10.patchapplication/octet-stream; name=catalog_multi_insert-v10.patch; x-unix-mode=0644Download
From bceacd117700cf61ceb7eac9eefead15d6292297 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 24 Jun 2020 17:19:56 +0200
Subject: [PATCH] Use heap_multi_insert for catalog relations, v10

Introduce a new function CatalogMultiInsertWithInfo which can replace
multiple calls to CatalogTupleInsertWithInfo by instead taking set of
slots to perform heap_multi_insert on. InsertPgAttributeTuples is also
introduced as a way to insert multiple attributes at once, and replaces
InsertPgAttributeTuple.

For insertions performing recordDependencyOn or InsertPgAttributeTuple
in a loop context, move these to collecting a set of tuples and use the
new InsertPgAttributeTuples or recordMultipleDependencies instead. Also
make recordMultipleDependencies use CatalogMultiInsertWithInfo to ensure
insertion via heap_multi_insert.
---
 src/backend/access/heap/heapam.c           |   4 -
 src/backend/catalog/dependency.c           |  16 ++
 src/backend/catalog/heap.c                 | 206 ++++++++++++---------
 src/backend/catalog/index.c                |  58 +++---
 src/backend/catalog/indexing.c             |  36 ++++
 src/backend/catalog/pg_aggregate.c         |  63 +++----
 src/backend/catalog/pg_constraint.c        |  76 ++++----
 src/backend/catalog/pg_depend.c            |  96 ++++++++--
 src/backend/catalog/pg_operator.c          |  52 +++---
 src/backend/catalog/pg_proc.c              |  52 +++---
 src/backend/catalog/pg_shdepend.c          |  54 ++++--
 src/backend/catalog/pg_type.c              |  51 +++--
 src/backend/commands/extension.c           |  25 +--
 src/backend/commands/tablecmds.c           |   9 +-
 src/include/catalog/dependency.h           |   2 +
 src/include/catalog/heap.h                 |   9 +-
 src/include/catalog/indexing.h             |   5 +
 src/test/regress/expected/create_index.out |   6 +-
 18 files changed, 473 insertions(+), 347 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 537913d1bb..3334bef458 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2168,10 +2168,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			RelationPutHeapTuple(relation, buffer, heaptup, false);
 
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
 			if (needwal && need_cids)
 				log_heap_new_cid(relation, heaptup);
 		}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index b33a2f94af..edba9f1ca7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2698,6 +2698,22 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+/*
+ * Clear an ObjectAddresses array such that it can be reused to avoid an
+ * allocation cycle.
+ */
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3c83fe6bab..f1e6898e4a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -710,12 +710,18 @@ CheckAttributeType(const char *attname,
 }
 
 /*
- * InsertPgAttributeTuple
- *		Construct and insert a new tuple in pg_attribute.
+ * Cap the maximum amount of bytes allocated for InsertPgAttributeTuples slots
+ */
+#define MAX_ATTRINSERT_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert a set of tuple in pg_attribute.
  *
- * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl,
- * attfdwoptions and attmissingval are always initialized to NULL.
+ * Caller has already opened and locked pg_attribute. tupdesc contains the
+ * attributes to insert.  attcacheoff is always initialized to -1, attacl,
+ * attfdwoptions and attmissingval are always initialized to NULL. attoptions
+ * must contain the same number of elements as tupdesc, or be NULL.
  *
  * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
  * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
@@ -723,57 +729,92 @@ CheckAttributeType(const char *attname,
  * expensive.)
  */
 void
-InsertPgAttributeTuple(Relation pg_attribute_rel,
-					   Form_pg_attribute new_attribute,
-					   Datum attoptions,
-					   CatalogIndexState indstate)
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						Datum *attoptions,
+						CatalogIndexState indstate)
 {
-	Datum		values[Natts_pg_attribute];
-	bool		nulls[Natts_pg_attribute];
-	HeapTuple	tup;
+	TupleTableSlot	  **slot;
+	TupleDesc			td;
+	int					nslots;
+	int					natts = 0;
+	int					count = 0;
+	bool				close_index = false;
+
+	td = RelationGetDescr(pg_attribute_rel);
+
+	nslots = Min(tupdesc->natts,
+				 (MAX_ATTRINSERT_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
+
+	while (natts < tupdesc->natts)
+	{
+		Form_pg_attribute		attrs = TupleDescAttr(tupdesc, natts);
 
-	/* This is a tad tedious, but way cleaner than what we used to do... */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
+		ExecClearTuple(slot[count]);
+
+		if (new_rel_oid != InvalidOid)
+			slot[count]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[count]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(attrs->attrelid);
+
+		slot[count]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&attrs->attname);
+		slot[count]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(attrs->atttypid);
+		slot[count]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(attrs->attstattarget);
+		slot[count]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(attrs->attlen);
+		slot[count]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attnum);
+		slot[count]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(attrs->attndims);
+		slot[count]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[count]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(attrs->atttypmod);
+		slot[count]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(attrs->attbyval);
+		slot[count]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(attrs->attstorage);
+		slot[count]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(attrs->attalign);
+		slot[count]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(attrs->attnotnull);
+		slot[count]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(attrs->atthasdef);
+		slot[count]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(attrs->atthasmissing);
+		slot[count]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(attrs->attidentity);
+		slot[count]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(attrs->attgenerated);
+		slot[count]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(attrs->attisdropped);
+		slot[count]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(attrs->attislocal);
+		slot[count]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(attrs->attinhcount);
+		slot[count]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(attrs->attcollation);
+		if (attoptions && attoptions[natts] != (Datum) 0)
+			slot[count]->tts_values[Anum_pg_attribute_attoptions - 1] = attoptions[natts];
+		else
+			slot[count]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
 
-	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
-	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
-	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
-	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
-	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
-	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
-	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
-	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
-	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
-	values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
-	values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
-	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
-	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
-	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
-	values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
-	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
-	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
-	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
-	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
-	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
-	values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
-	values[Anum_pg_attribute_attoptions - 1] = attoptions;
-
-	/* start out with empty permissions and empty options */
-	nulls[Anum_pg_attribute_attacl - 1] = true;
-	nulls[Anum_pg_attribute_attoptions - 1] = attoptions == (Datum) 0;
-	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
-	nulls[Anum_pg_attribute_attmissingval - 1] = true;
-
-	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		/* start out with empty permissions and empty options */
+		slot[count]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[count]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[count]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
 
-	/* finally insert the new tuple, update the indexes, and clean up */
-	if (indstate != NULL)
-		CatalogTupleInsertWithInfo(pg_attribute_rel, tup, indstate);
-	else
-		CatalogTupleInsert(pg_attribute_rel, tup);
+		ExecStoreVirtualTuple(slot[count]);
+		count++;
 
-	heap_freetuple(tup);
+		if (count == nslots || natts == tupdesc->natts - 1)
+		{
+			if (!indstate)
+			{
+				indstate = CatalogOpenIndexes(pg_attribute_rel);
+				close_index = true;
+			}
+
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, count, indstate);
+			count = 0;
+		}
+
+		natts++;
+	}
+
+	if (close_index)
+		CatalogCloseIndexes(indstate);
+
+	for (int i = 0; i < Min(tupdesc->natts, nslots); i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /* --------------------------------
@@ -788,8 +829,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -803,35 +842,31 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+
+	for (int i = 0; i < natts; i++)
+		tupdesc->attrs[i].attstattarget = -1;
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * Now add dependencies on their datatypes and collations
 	 */
-	for (i = 0; i < natts; i++)
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, (Datum) 0, indstate);
-
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = tupdesc->attrs[i].atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = tupdesc->attrs[i].attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -844,17 +879,12 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
+		TupleDesc	td;
 
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
+		td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, (Datum) 0, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
@@ -3537,6 +3567,7 @@ StorePartitionKey(Relation rel,
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3584,27 +3615,32 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* Operator class and collation per key column */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
+		ObjectAddress	ocobj;
+
+		ObjectAddressSet(ocobj, OperatorClassRelationId, partopclass[i]);
+		add_exact_object_address(&ocobj, refobjs);
 
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
+			ObjectAddress	collobj;
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSet(collobj, CollationRelationId, partcollation[i]);
+			add_exact_object_address(&collobj, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * The partitioning columns are made internally dependent on the table,
 	 * because we cannot drop any of them without dropping the whole table.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index cdc01c49c9..8c7adec888 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -106,8 +106,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, int numatts,
-								  Datum *attopts);
+static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -485,12 +484,11 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
+AppendAttributeTuples(Relation indexRelation, Datum *attopts)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
 	TupleDesc	indexTupDesc;
-	int			i;
 
 	/*
 	 * open the attribute relation and its indexes
@@ -504,15 +502,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-		Datum		attoptions = attopts ? attopts[i] : (Datum) 0;
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, attoptions, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attopts, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -979,8 +969,7 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs,
-						  indexInfo->ii_OpclassOptions);
+	AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions);
 
 	/* ----------------
 	 *	  update pg_index
@@ -1029,11 +1018,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1070,14 +1062,14 @@ index_create(Relation heapRelation,
 			/* Create auto dependencies on simply-referenced columns */
 			for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 			{
+				ObjectAddress obj;
+
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					ObjectAddressSubSet(obj, RelationRelationId,
+										heapRelationId,
+										indexInfo->ii_IndexAttrNumbers[i]);
+					add_exact_object_address(&obj, refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1096,6 +1088,8 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
 		}
 
 		/*
@@ -1119,31 +1113,33 @@ index_create(Relation heapRelation,
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 		}
 
+
 		/* Store dependency on collations */
 		/* The default collation is pinned, so don't bother recording it */
+		reset_object_addresses(refobjs);
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
+			ObjectAddress	obj;
+
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				ObjectAddressSet(obj, CollationRelationId, collationObjectId[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			ObjectAddress	obj;
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSet(obj, OperatorClassRelationId, classObjectId[i]);
+			add_exact_object_address(&obj, refobjs);
 		}
 
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
 		{
@@ -1163,6 +1159,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index d63fcf58cf..b98d4bffbd 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 7d887ea24a..7e525b8828 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -103,8 +103,9 @@ AggregateCreate(const char *aggName,
 	TupleDesc	tupDesc;
 	char	   *detailmsg;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -741,84 +742,70 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSet(obj, ProcedureRelationId, transfn);
+	add_exact_object_address(&obj, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, finalfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, combinefn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, serialfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, deserialfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, mtransfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, minvtransfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, mfinalfn, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
 	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, OperatorRelationId, sortop, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+
 	return myself;
 }
 
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 90932be831..cea63a6f4a 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -91,6 +91,8 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -229,30 +231,26 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
 			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+				ObjectAddressSubSet(obj, RelationRelationId, relId, constraintKey[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			ObjectAddressSubSet(obj, RelationRelationId, relId, 0);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -261,14 +259,14 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
+		ObjectAddressSubSet(obj, TypeRelationId, domainId, 0);
+		add_exact_object_address(&obj, refobjs);
+	}
 
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
+	/* record the AUTO dependencies we have so far */
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
 
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
-	}
+	reset_object_addresses(refobjs);
 
 	if (OidIsValid(foreignRelId))
 	{
@@ -276,24 +274,18 @@ CreateConstraintEntry(const char *constraintName,
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
 			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+				ObjectAddressSubSet(obj, RelationRelationId, foreignRelId, foreignKey[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			ObjectAddressSubSet(obj, RelationRelationId, foreignRelId, 0);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -305,13 +297,8 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, RelationRelationId, indexRelId, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -322,28 +309,29 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			ObjectAddressSubSet(obj, OperatorRelationId, pfEqOp[i], 0);
+			add_exact_object_address(&obj, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
 			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+				ObjectAddressSubSet(obj, OperatorRelationId, ppEqOp[i], 0);
+				add_exact_object_address(&obj, refobjs);
 			}
+
 			if (ffEqOp[i] != pfEqOp[i])
 			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+				ObjectAddressSubSet(obj, OperatorRelationId, ffEqOp[i], 0);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 21cfdcace9..8d743e49c5 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -35,7 +35,8 @@ static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
- * references.
+ * references. This is a simplified version of recordMultipleDependencies()
+ * aiming to avoid some overhead when we know there is only a single tuple.
  *
  * This simply creates an entry in pg_depend, without any other processing.
  */
@@ -44,7 +45,51 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, behavior);
+	Relation	dependDesc;
+	HeapTuple	tuple;
+	bool		nulls[Natts_pg_depend];
+	Datum		values[Natts_pg_depend];
+
+	/*
+	 * During bootstrap, do nothing since pg_depend may not exist yet. initdb
+	 * will fill in appropriate pg_depend entries after bootstrap.
+	 */
+	if (IsBootstrapProcessingMode())
+		return;
+
+	dependDesc = table_open(DependRelationId, RowExclusiveLock);
+
+	memset(nulls, false, sizeof(nulls));
+
+	/*
+	 * If the referenced object is pinned by the system, there's no real
+	 * need to record dependencies on it.  This saves lots of space in
+	 * pg_depend, so it's worth the time taken to check.
+	 */
+	if (isObjectPinned(referenced, dependDesc))
+	{
+		table_close(dependDesc, RowExclusiveLock);
+		return;
+	}
+
+	/*
+	 * Record the Dependency.  Note we don't bother to check for
+	 * duplicate dependencies; there's no harm in them.
+	 */
+	values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+	values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+	values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+	values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+	values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+	values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+	values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+
+	tuple = heap_form_tuple(dependDesc->rd_att, values, nulls);
+	CatalogTupleInsert(dependDesc, tuple);
+
+	table_close(dependDesc, RowExclusiveLock);
 }
 
 /*
@@ -59,10 +104,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
-	bool		nulls[Natts_pg_depend];
-	Datum		values[Natts_pg_depend];
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -79,9 +123,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -90,34 +135,47 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		 */
 		if (!isObjectPinned(referenced, dependDesc))
 		{
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+			ExecClearTuple(slot[ntuples]);
 			/*
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+
+			slot[ntuples]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
-			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
-			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
 
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+			memset(slot[ntuples]->tts_isnull, false,
+				   slot[ntuples]->tts_tupleDescriptor->natts * sizeof(bool));
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			ExecStoreVirtualTuple(slot[ntuples]);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
+
+	for (int i = 0; i < ntuples; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 340e284ae4..4e80438650 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,16 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -793,37 +796,29 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
 	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, NamespaceRelationId, oper->oprnamespace, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TypeRelationId, oper->oprleft, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TypeRelationId, oper->oprright, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TypeRelationId, oper->oprresult, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/*
@@ -838,30 +833,27 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, oper->oprcode, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, oper->oprrest, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, oper->oprjoin, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
 							oper->oprowner);
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 6cdda35d1c..6814820b71 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -109,8 +109,9 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress	myself;
+	ObjectAddresses  *refobjs;
+	ObjectAddress	obj;
 	char	   *detailmsg;
 	int			i;
 	Oid			trfid;
@@ -589,48 +590,38 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSubSet(obj, NamespaceRelationId, procNamespace, 0);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSubSet(obj, LanguageRelationId, languageObjectId, 0);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSubSet(obj, TypeRelationId, returnType, 0);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
 	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TransformRelationId, trfid, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, TypeRelationId, allParams[i], 0);
+		add_exact_object_address(&obj, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
 		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSubSet(obj, TransformRelationId, trfid, 0);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -642,12 +633,13 @@ ProcedureCreate(const char *procedureName,
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(obj, ProcedureRelationId, prosupport, 0);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* dependency on owner */
 	if (!is_update)
 		recordDependencyOnOwner(ProcedureRelationId, retval, proowner);
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index f776e821b3..6c66134f9a 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -785,6 +785,8 @@ checkSharedDependencies(Oid classId, Oid objectId,
 	return true;
 }
 
+#define MAX_TEMPLATE_BYTES 65535
+
 /*
  * copyTemplateDependencies
  *
@@ -799,14 +801,19 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	int			slotCount;
 	CatalogIndexState indstate;
-	Datum		values[Natts_pg_shdepend];
-	bool		nulls[Natts_pg_shdepend];
-	bool		replace[Natts_pg_shdepend];
+	TupleTableSlot **slot;
+	int			nslots;
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
 
+	nslots = MAX_TEMPLATE_BYTES / sizeof(FormData_pg_shdepend);
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+
 	indstate = CatalogOpenIndexes(sdepRel);
 
 	/* Scan all entries with dbid = templateDbId */
@@ -818,14 +825,6 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
 							  NULL, 1, key);
 
-	/* Set up to copy the tuples except for inserting newDbId */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
-	memset(replace, false, sizeof(replace));
-
-	replace[Anum_pg_shdepend_dbid - 1] = true;
-	values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(newDbId);
-
 	/*
 	 * Copy the entries of the original database, changing the database Id to
 	 * that of the new database.  Note that because we are not copying rows
@@ -833,20 +832,45 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	slotCount = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
+		Form_pg_shdepend shdep;
 
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		ExecClearTuple(slot[slotCount]);
 
-		heap_freetuple(newtup);
+		shdep = (Form_pg_shdepend) GETSTRUCT(tup);
+
+		slot[slotCount]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId);
+		slot[slotCount]->tts_values[Anum_pg_shdepend_classid] = shdep->classid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objid] = shdep->objid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		if (slotCount == nslots)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (slotCount)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
 	table_close(sdepRel, RowExclusiveLock);
+
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /*
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index cd56714968..959e0b1327 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -553,7 +553,9 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 	Datum		datum;
 	bool		isNull;
 	ObjectAddress myself,
-				referenced;
+				referenced,
+				refobjs[8];
+	int			nref;
 
 	/* Extract defaultExpr if caller didn't pass it */
 	if (defaultExpr == NULL)
@@ -605,63 +607,54 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 		recordDependencyOnCurrentExtension(&myself, rebuild);
 	}
 
+	nref = 0;
+
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typinput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typoutput);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typreceive);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typsend);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodin);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typmodout);
+		nref++;
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(refobjs[nref], ProcedureRelationId, typeForm->typanalyze);
+		nref++;
 	}
 
+	if (nref)
+		recordMultipleDependencies(&myself, refobjs, nref, DEPENDENCY_NORMAL);
+
 	/*
 	 * If the type is a rowtype for a relation, mark it as internally
 	 * dependent on the relation, *unless* it is a stand-alone composite type
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 472e69fdaf..f5c44bc551 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1782,7 +1782,8 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	bool		nulls[Natts_pg_extension];
 	HeapTuple	tuple;
 	ObjectAddress myself;
-	ObjectAddress nsp;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 	ListCell   *lc;
 
 	/*
@@ -1823,29 +1824,29 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	/*
 	 * Record dependencies on owner, schema, and prerequisite extensions
 	 */
+	refobjs = new_object_addresses();
+
 	recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
 
 	myself.classId = ExtensionRelationId;
 	myself.objectId = extensionOid;
 	myself.objectSubId = 0;
 
-	nsp.classId = NamespaceRelationId;
-	nsp.objectId = schemaOid;
-	nsp.objectSubId = 0;
-
-	recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+	ObjectAddressSubSet(obj, NamespaceRelationId, schemaOid, 0);
+	add_exact_object_address(&obj, refobjs);
 
 	foreach(lc, requiredExtensions)
 	{
 		Oid			reqext = lfirst_oid(lc);
-		ObjectAddress otherext;
-
-		otherext.classId = ExtensionRelationId;
-		otherext.objectId = reqext;
-		otherext.objectSubId = 0;
+		ObjectAddress reqobj;
 
-		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+		ObjectAddressSubSet(reqobj, ExtensionRelationId, reqext, 0);
+		add_exact_object_address(&reqobj, refobjs);
 	}
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f39f..f876d8fc84 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5975,7 +5975,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	AlterTableCmd *childcmd;
 	AclResult	aclresult;
 	ObjectAddress address;
-
+	TupleDesc	tupdesc;
+	FormData_pg_attribute *aattr[] = {&attribute};
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
@@ -6128,11 +6129,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
-	/* attribute.attacl is handled by InsertPgAttributeTuple */
+	/* attribute.attacl is handled by InsertPgAttributeTuples */
 
 	ReleaseSysCache(typeTuple);
 
-	InsertPgAttributeTuple(attrdesc, &attribute, (Datum) 0, NULL);
+	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index a8f7e9965b..b7626a7ecf 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -178,6 +178,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2abe..d31141c1a2 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -93,10 +93,11 @@ extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
 extern List *heap_truncate_find_FKs(List *relationIds);
 
-extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
-								   Form_pg_attribute new_attribute,
-								   Datum attoptions,
-								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									Datum *attoptions,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..a7e2a9b26b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index e3e6634d7e..814416d936 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2069,11 +2069,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2097,11 +2096,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
-- 
2.21.1 (Apple Git-122.3)

#35Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#34)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Thu, Jun 25, 2020 at 09:38:23AM +0200, Daniel Gustafsson wrote:

Attached is a rebased version which was updated to handle the changes for op
class parameters introduced in 911e70207703799605.

Thanks for the updated version.

While re-reading the code, I got cold feet with the changes done in
recordDependencyOn(). Sure, we could do as you propose, but that does
not have to be part of this patch I think, aimed at switching more
catalogs to use multi inserts, and it just feels a duplicate of
recordMultipleDependencies(), with the same comments copied all over
the place, etc.

MAX_TEMPLATE_BYTES in pg_shdepend.c needs a comment to explain that
this is to cap the number of slots used in
copyTemplateDependencies() for pg_shdepend.

Not much a fan of the changes in GenerateTypeDependencies(),
particularly the use of refobjs[8], capped to the number of items from
typeForm. If we add new members I think that this would break
easily without us actually noticing that it broke. The use of
ObjectAddressSet() is a good idea though, but it does not feel
consistent if you don't the same coding rule to typbasetype,
typcollation or typelem. I am also thinking to split this part of the
cleanup in a first independent patch.

pg_constraint.c, pg_operator.c, extension.c and pg_aggregate.c were
using ObjectAddressSubSet() with subset set to 0 when registering a
dependency. It is simpler to just use ObjectAddressSet(). As this
updates the way dependencies are tracked and recorded, that's better
if kept in the main patch.

+   /* TODO is nreferenced a reasonable allocation of slots? */
+   slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
It seems to me that we could just apply the same rule as for
pg_attribute and pg_shdepend, no?

CatalogTupleInsertWithInfo() becomes mostly unused with this patch,
its only caller being now LOs. Just noticing, I'd rather not remove
it for now.

The attached includes a bunch of modifications I have done while going
through the patch (I indend to split and apply the changes of
pg_type.c separately first, just lacked of time now to send a proper
split), and there is the number of slots for pg_depend insertions that
still needs to be addressed. On top of that pgindent has not been run
yet. That's all I have for today, overall the patch is taking a
committable shape :)
--
Michael

Attachments:

catalog_multi_insert-v11.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index a8f7e9965b..b7626a7ecf 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -178,6 +178,8 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
 
 extern void sort_object_addresses(ObjectAddresses *addrs);
 
+extern void reset_object_addresses(ObjectAddresses *addrs);
+
 extern void free_object_addresses(ObjectAddresses *addrs);
 
 /* in pg_depend.c */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2abe..d31141c1a2 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -93,10 +93,11 @@ extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
 extern List *heap_truncate_find_FKs(List *relationIds);
 
-extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
-								   Form_pg_attribute new_attribute,
-								   Datum attoptions,
-								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									Datum *attoptions,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..a7e2a9b26b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 537913d1bb..3334bef458 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2168,10 +2168,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			RelationPutHeapTuple(relation, buffer, heaptup, false);
 
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
 			if (needwal && need_cids)
 				log_heap_new_cid(relation, heaptup);
 		}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index b33a2f94af..edba9f1ca7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2698,6 +2698,22 @@ sort_object_addresses(ObjectAddresses *addrs)
 			  object_address_comparator);
 }
 
+/*
+ * Clear an ObjectAddresses array such that it can be reused to avoid an
+ * allocation cycle.
+ */
+void
+reset_object_addresses(ObjectAddresses *addrs)
+{
+	if (addrs->numrefs == 0)
+		return;
+
+	memset(addrs->refs, 0, addrs->maxrefs * sizeof(ObjectAddress));
+	if (addrs->extras)
+		memset(addrs->extras, 0, addrs->maxrefs * sizeof(ObjectAddressExtra));
+	addrs->numrefs = 0;
+}
+
 /*
  * Clean up when done with an ObjectAddresses array.
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3c83fe6bab..1a37bb2d0e 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -710,70 +710,115 @@ CheckAttributeType(const char *attname,
 }
 
 /*
- * InsertPgAttributeTuple
- *		Construct and insert a new tuple in pg_attribute.
+ * Cap the maximum amount of bytes allocated for InsertPgAttributeTuples
+ * slots.
+ */
+#define MAX_ATTRINSERT_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert a set of tuples in pg_attribute.
  *
- * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl,
- * attfdwoptions and attmissingval are always initialized to NULL.
+ * Caller has already opened and locked pg_attribute. tupdesc contains the
+ * attributes to insert.  attcacheoff is always initialized to -1, attacl,
+ * attfdwoptions and attmissingval are always initialized to NULL.  attoptions
+ * must contain the same number of elements as tupdesc, or be NULL.
  *
  * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
  * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
  * this when inserting multiple attributes, because it's a tad more
  * expensive.)
+ *
+ * new_rel_oid is the relation OID assigned to the attributes inserted.
+ * If set to InvalidOid, the relation OID from tupdesc is used instead.
  */
 void
-InsertPgAttributeTuple(Relation pg_attribute_rel,
-					   Form_pg_attribute new_attribute,
-					   Datum attoptions,
-					   CatalogIndexState indstate)
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						Datum *attoptions,
+						CatalogIndexState indstate)
 {
-	Datum		values[Natts_pg_attribute];
-	bool		nulls[Natts_pg_attribute];
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	TupleDesc	td;
+	int			nslots;
+	int			natts = 0;
+	int			count = 0;
+	bool		close_index = false;
 
-	/* This is a tad tedious, but way cleaner than what we used to do... */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
+	td = RelationGetDescr(pg_attribute_rel);
 
-	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
-	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
-	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
-	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
-	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
-	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
-	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
-	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
-	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
-	values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
-	values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
-	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
-	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
-	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
-	values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
-	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
-	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
-	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
-	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
-	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
-	values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
-	values[Anum_pg_attribute_attoptions - 1] = attoptions;
+	nslots = Min(tupdesc->natts,
+				 (MAX_ATTRINSERT_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
 
-	/* start out with empty permissions and empty options */
-	nulls[Anum_pg_attribute_attacl - 1] = true;
-	nulls[Anum_pg_attribute_attoptions - 1] = attoptions == (Datum) 0;
-	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
-	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+	while (natts < tupdesc->natts)
+	{
+		Form_pg_attribute attrs = TupleDescAttr(tupdesc, natts);
 
-	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecClearTuple(slot[count]);
 
-	/* finally insert the new tuple, update the indexes, and clean up */
-	if (indstate != NULL)
-		CatalogTupleInsertWithInfo(pg_attribute_rel, tup, indstate);
-	else
-		CatalogTupleInsert(pg_attribute_rel, tup);
+		if (new_rel_oid != InvalidOid)
+			slot[count]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[count]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(attrs->attrelid);
 
-	heap_freetuple(tup);
+		slot[count]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&attrs->attname);
+		slot[count]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(attrs->atttypid);
+		slot[count]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(attrs->attstattarget);
+		slot[count]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(attrs->attlen);
+		slot[count]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attnum);
+		slot[count]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(attrs->attndims);
+		slot[count]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[count]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(attrs->atttypmod);
+		slot[count]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(attrs->attbyval);
+		slot[count]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(attrs->attstorage);
+		slot[count]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(attrs->attalign);
+		slot[count]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(attrs->attnotnull);
+		slot[count]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(attrs->atthasdef);
+		slot[count]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(attrs->atthasmissing);
+		slot[count]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(attrs->attidentity);
+		slot[count]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(attrs->attgenerated);
+		slot[count]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(attrs->attisdropped);
+		slot[count]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(attrs->attislocal);
+		slot[count]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(attrs->attinhcount);
+		slot[count]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(attrs->attcollation);
+		if (attoptions && attoptions[natts] != (Datum) 0)
+			slot[count]->tts_values[Anum_pg_attribute_attoptions - 1] = attoptions[natts];
+		else
+			slot[count]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
+
+		/* start out with empty permissions and empty options */
+		slot[count]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[count]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[count]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
+
+		ExecStoreVirtualTuple(slot[count]);
+		count++;
+
+		if (count == nslots || natts == tupdesc->natts - 1)
+		{
+			if (!indstate)
+			{
+				indstate = CatalogOpenIndexes(pg_attribute_rel);
+				close_index = true;
+			}
+
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, count, indstate);
+			count = 0;
+		}
+
+		natts++;
+	}
+
+	if (close_index)
+		CatalogCloseIndexes(indstate);
+
+	for (int i = 0; i < Min(tupdesc->natts, nslots); i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /* --------------------------------
@@ -788,8 +833,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -803,35 +846,31 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
+	/* set stats detail level to a sane default */
+	for (int i = 0; i < natts; i++)
+		tupdesc->attrs[i].attstattarget = -1;
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate);
+
 	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
+	 * Now add dependencies on their datatypes and collations
 	 */
-	for (i = 0; i < natts; i++)
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, (Datum) 0, indstate);
-
 		/* Add dependency info */
 		myself.classId = RelationRelationId;
 		myself.objectId = new_rel_oid;
 		myself.objectSubId = i + 1;
 		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
+		referenced.objectId = tupdesc->attrs[i].atttypid;
 		referenced.objectSubId = 0;
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
 			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
+			referenced.objectId = tupdesc->attrs[i].attcollation;
 			referenced.objectSubId = 0;
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
@@ -844,17 +883,12 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
+		TupleDesc	td;
 
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
+		td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, (Datum) 0, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
@@ -3537,6 +3571,7 @@ StorePartitionKey(Relation rel,
 	bool		nulls[Natts_pg_partitioned_table];
 	ObjectAddress myself;
 	ObjectAddress referenced;
+	ObjectAddresses *refobjs;
 
 	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -3584,27 +3619,34 @@ StorePartitionKey(Relation rel,
 	myself.objectId = RelationGetRelid(rel);
 	myself.objectSubId = 0;
 
-	/* Operator class and collation per key column */
+	refobjs = new_object_addresses();
+
+	/*
+	 * Operator class and collation per key column.  Gather all the
+	 * normal dependencies first, and record them all at once.
+	 */
 	for (i = 0; i < partnatts; i++)
 	{
-		referenced.classId = OperatorClassRelationId;
-		referenced.objectId = partopclass[i];
-		referenced.objectSubId = 0;
+		ObjectAddress ocobj;
 
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(ocobj, OperatorClassRelationId, partopclass[i]);
+		add_exact_object_address(&ocobj, refobjs);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
 			partcollation[i] != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = partcollation[i];
-			referenced.objectSubId = 0;
+			ObjectAddress collobj;
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSet(collobj, CollationRelationId, partcollation[i]);
+			add_exact_object_address(&collobj, refobjs);
 		}
 	}
 
+	/* Store the dependencies in the catalog */
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/*
 	 * The partitioning columns are made internally dependent on the table,
 	 * because we cannot drop any of them without dropping the whole table.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index cdc01c49c9..b19492b683 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -106,8 +106,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, int numatts,
-								  Datum *attopts);
+static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -485,12 +484,11 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
+AppendAttributeTuples(Relation indexRelation, Datum *attopts)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
 	TupleDesc	indexTupDesc;
-	int			i;
 
 	/*
 	 * open the attribute relation and its indexes
@@ -504,15 +502,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-		Datum		attoptions = attopts ? attopts[i] : (Datum) 0;
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, attoptions, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attopts, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -979,8 +969,7 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs,
-						  indexInfo->ii_OpclassOptions);
+	AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions);
 
 	/* ----------------
 	 *	  update pg_index
@@ -1029,11 +1018,14 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ObjectAddresses *refobjs;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
 		myself.objectSubId = 0;
 
+		refobjs = new_object_addresses();
+
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
 			char		constraintType;
@@ -1070,14 +1062,14 @@ index_create(Relation heapRelation,
 			/* Create auto dependencies on simply-referenced columns */
 			for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 			{
+				ObjectAddress obj;
+
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
-					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
+					ObjectAddressSubSet(obj, RelationRelationId,
+										heapRelationId,
+										indexInfo->ii_IndexAttrNumbers[i]);
+					add_exact_object_address(&obj, refobjs);
 					have_simple_col = true;
 				}
 			}
@@ -1096,6 +1088,8 @@ index_create(Relation heapRelation,
 
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
+			else
+				record_object_address_dependencies(&myself, refobjs, DEPENDENCY_AUTO);
 		}
 
 		/*
@@ -1121,29 +1115,30 @@ index_create(Relation heapRelation,
 
 		/* Store dependency on collations */
 		/* The default collation is pinned, so don't bother recording it */
+		reset_object_addresses(refobjs);
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
+			ObjectAddress obj;
+
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				ObjectAddressSet(obj, CollationRelationId, collationObjectId[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			ObjectAddress obj;
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSet(obj, OperatorClassRelationId, classObjectId[i]);
+			add_exact_object_address(&obj, refobjs);
 		}
 
+		record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
 		{
@@ -1163,6 +1158,8 @@ index_create(Relation heapRelation,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO, false);
 		}
+
+		free_object_addresses(refobjs);
 	}
 	else
 	{
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index d63fcf58cf..1eabeb52ca 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo - as above, but for multiple tuples
+ *
+ * Insert multiple tuples into the catalog relation at once, with an amortized
+ * cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 7d887ea24a..7eb262edd7 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -103,8 +103,9 @@ AggregateCreate(const char *aggName,
 	TupleDesc	tupDesc;
 	char	   *detailmsg;
 	int			i;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 	AclResult	aclresult;
 
 	/* sanity checks (caller should have caught these) */
@@ -741,84 +742,70 @@ AggregateCreate(const char *aggName,
 	 * way.
 	 */
 
+	refobjs = new_object_addresses();
+
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSet(obj, ProcedureRelationId, transfn);
+	add_exact_object_address(&obj, refobjs);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, finalfn);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, combinefn);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, serialfn);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, deserialfn);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, mtransfn);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, minvtransfn);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, mfinalfn);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
 	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, OperatorRelationId, sortop);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+
 	return myself;
 }
 
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 90932be831..18cbb706b6 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -91,6 +91,8 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -229,30 +231,30 @@ CreateConstraintEntry(const char *constraintName,
 
 	table_close(conDesc, RowExclusiveLock);
 
+	refobjs = new_object_addresses();
+
+	/*
+	 * Build all the auto dependencies to track.  These are all recorded at
+	 * once.
+	 */
 	if (OidIsValid(relId))
 	{
 		/*
 		 * Register auto dependency from constraint to owning relation, or to
 		 * specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
 			{
-				relobject.objectSubId = constraintKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+				ObjectAddressSubSet(obj, RelationRelationId, relId, constraintKey[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
+			ObjectAddressSet(obj, RelationRelationId, relId);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -261,39 +263,36 @@ CreateConstraintEntry(const char *constraintName,
 		/*
 		 * Register auto dependency from constraint to owning domain
 		 */
-		ObjectAddress domobject;
-
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
+		ObjectAddressSet(obj, TypeRelationId, domainId);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_AUTO);
+
+	reset_object_addresses(refobjs);
+
+	/*
+	 * Build all the normal dependencies to track.  These are all recorded
+	 * at once.
+	 */
 	if (OidIsValid(foreignRelId))
 	{
 		/*
 		 * Register normal dependency from constraint to foreign relation, or
 		 * to specific column(s) if any are mentioned.
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
 			{
-				relobject.objectSubId = foreignKey[i];
-
-				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+				ObjectAddressSubSet(obj, RelationRelationId, foreignRelId, foreignKey[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
-			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+			ObjectAddressSet(obj, RelationRelationId, foreignRelId);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -305,13 +304,8 @@ CreateConstraintEntry(const char *constraintName,
 		 * or primary-key constraints, the dependency runs the other way, and
 		 * is not made here.)
 		 */
-		ObjectAddress relobject;
-
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
-		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, RelationRelationId, indexRelId);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	if (foreignNKeys > 0)
@@ -322,28 +316,29 @@ CreateConstraintEntry(const char *constraintName,
 		 * all three operators for a column are the same; otherwise they are
 		 * different.
 		 */
-		ObjectAddress oprobject;
-
-		oprobject.classId = OperatorRelationId;
-		oprobject.objectSubId = 0;
-
 		for (i = 0; i < foreignNKeys; i++)
 		{
-			oprobject.objectId = pfEqOp[i];
-			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			ObjectAddressSet(obj, OperatorRelationId, pfEqOp[i]);
+			add_exact_object_address(&obj, refobjs);
+
 			if (ppEqOp[i] != pfEqOp[i])
 			{
-				oprobject.objectId = ppEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+				ObjectAddressSet(obj, OperatorRelationId, ppEqOp[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
+
 			if (ffEqOp[i] != pfEqOp[i])
 			{
-				oprobject.objectId = ffEqOp[i];
-				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+				ObjectAddressSet(obj, OperatorRelationId, ffEqOp[i]);
+				add_exact_object_address(&obj, refobjs);
 			}
 		}
 	}
 
+	record_object_address_dependencies(&conobject, refobjs, DEPENDENCY_NORMAL);
+
+	free_object_addresses(refobjs);
+
 	/*
 	 * We don't bother to register dependencies on the exclusion operators of
 	 * an exclusion constraint.  We assume they are members of the opclass
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 21cfdcace9..92effb3b86 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -59,10 +59,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	int			ntuples;
 	int			i;
-	bool		nulls[Natts_pg_depend];
-	Datum		values[Natts_pg_depend];
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -79,9 +78,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
+	/* TODO is nreferenced a reasonable allocation of slots? */
+	slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	for (i = 0, ntuples = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
@@ -90,34 +90,48 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		 */
 		if (!isObjectPinned(referenced, dependDesc))
 		{
+			slot[ntuples] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+													 &TTSOpsHeapTuple);
+			ExecClearTuple(slot[ntuples]);
+
 			/*
 			 * Record the Dependency.  Note we don't bother to check for
 			 * duplicate dependencies; there's no harm in them.
 			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
 
-			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
-			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
-			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+			slot[ntuples]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
 
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+			slot[ntuples]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+			slot[ntuples]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+			memset(slot[ntuples]->tts_isnull, false,
+				   slot[ntuples]->tts_tupleDescriptor->natts * sizeof(bool));
+
+			ExecStoreVirtualTuple(slot[ntuples]);
+			ntuples++;
 
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
-
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
 		}
 	}
 
+	/*
+	 * We will have an indstate in case we found any tuples to insert in the
+	 * catalog, so perform a multi insert and close the index again when done.
+	 */
 	if (indstate != NULL)
+	{
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, ntuples, indstate);
 		CatalogCloseIndexes(indstate);
+	}
+
+	for (int i = 0; i < ntuples; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
 
 	table_close(dependDesc, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 340e284ae4..f2360488c9 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -773,13 +773,16 @@ ObjectAddress
 makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 {
 	Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple);
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 
 	myself.classId = OperatorRelationId;
 	myself.objectId = oper->oid;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/*
 	 * If we are updating the operator, delete any existing entries, except
 	 * for extension membership which should remain the same.
@@ -793,37 +796,29 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
 	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, NamespaceRelationId, oper->oprnamespace);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, TypeRelationId, oper->oprleft);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, TypeRelationId, oper->oprright);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, TypeRelationId, oper->oprresult);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/*
@@ -838,30 +833,27 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, oper->oprcode);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, oper->oprrest);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, oper->oprjoin);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Dependency on owner */
 	recordDependencyOnOwner(OperatorRelationId, oper->oid,
 							oper->oprowner);
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 6cdda35d1c..9395085e75 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -109,8 +109,9 @@ ProcedureCreate(const char *procedureName,
 	NameData	procname;
 	TupleDesc	tupDesc;
 	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	ObjectAddress myself;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 	char	   *detailmsg;
 	int			i;
 	Oid			trfid;
@@ -589,48 +590,38 @@ ProcedureCreate(const char *procedureName,
 	myself.objectId = retval;
 	myself.objectSubId = 0;
 
+	refobjs = new_object_addresses();
+
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSet(obj, NamespaceRelationId, procNamespace);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSet(obj, LanguageRelationId, languageObjectId);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	ObjectAddressSet(obj, TypeRelationId, returnType);
+	add_exact_object_address(&obj, refobjs);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
 	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, TransformRelationId, trfid);
+		add_exact_object_address(&obj, refobjs);
 	}
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, TypeRelationId, allParams[i]);
+		add_exact_object_address(&obj, refobjs);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
 		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			ObjectAddressSet(obj, TransformRelationId, trfid);
+			add_exact_object_address(&obj, refobjs);
 		}
 	}
 
@@ -642,12 +633,13 @@ ProcedureCreate(const char *procedureName,
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		ObjectAddressSet(obj, ProcedureRelationId, prosupport);
+		add_exact_object_address(&obj, refobjs);
 	}
 
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* dependency on owner */
 	if (!is_update)
 		recordDependencyOnOwner(ProcedureRelationId, retval, proowner);
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index f776e821b3..2092bcc74b 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -785,6 +785,13 @@ checkSharedDependencies(Oid classId, Oid objectId,
 	return true;
 }
 
+
+/*
+ * Cap the maximum amount of bytes allocated for copyTemplateDependencies
+ * slots.
+ */
+#define MAX_TEMPLATE_BYTES 65535
+
 /*
  * copyTemplateDependencies
  *
@@ -799,14 +806,19 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	int			slotCount;
 	CatalogIndexState indstate;
-	Datum		values[Natts_pg_shdepend];
-	bool		nulls[Natts_pg_shdepend];
-	bool		replace[Natts_pg_shdepend];
+	TupleTableSlot **slot;
+	int			nslots;
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
 
+	nslots = MAX_TEMPLATE_BYTES / sizeof(FormData_pg_shdepend);
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+
 	indstate = CatalogOpenIndexes(sdepRel);
 
 	/* Scan all entries with dbid = templateDbId */
@@ -818,14 +830,6 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
 							  NULL, 1, key);
 
-	/* Set up to copy the tuples except for inserting newDbId */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
-	memset(replace, false, sizeof(replace));
-
-	replace[Anum_pg_shdepend_dbid - 1] = true;
-	values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(newDbId);
-
 	/*
 	 * Copy the entries of the original database, changing the database Id to
 	 * that of the new database.  Note that because we are not copying rows
@@ -833,20 +837,45 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	slotCount = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
+		Form_pg_shdepend shdep;
 
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		ExecClearTuple(slot[slotCount]);
 
-		heap_freetuple(newtup);
+		shdep = (Form_pg_shdepend) GETSTRUCT(tup);
+
+		slot[slotCount]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId);
+		slot[slotCount]->tts_values[Anum_pg_shdepend_classid] = shdep->classid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objid] = shdep->objid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		if (slotCount == nslots)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (slotCount > 0)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
 	table_close(sdepRel, RowExclusiveLock);
+
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /*
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index cd56714968..79ffe317dd 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -579,9 +579,7 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 		deleteSharedDependencyRecordsFor(TypeRelationId, typeObjectId, 0);
 	}
 
-	myself.classId = TypeRelationId;
-	myself.objectId = typeObjectId;
-	myself.objectSubId = 0;
+	ObjectAddressSet(myself, TypeRelationId, typeObjectId);
 
 	/*
 	 * Make dependencies on namespace, owner, ACL, extension.
@@ -591,9 +589,8 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 	 */
 	if (!isDependentType)
 	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = typeForm->typnamespace;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, NamespaceRelationId,
+						 typeForm->typnamespace);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		recordDependencyOnOwner(TypeRelationId, typeObjectId,
@@ -608,57 +605,43 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 	/* Normal dependencies on the I/O functions */
 	if (OidIsValid(typeForm->typinput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typinput;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typinput);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	if (OidIsValid(typeForm->typoutput))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typoutput;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typoutput);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	if (OidIsValid(typeForm->typreceive))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typreceive;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typreceive);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	if (OidIsValid(typeForm->typsend))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typsend;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typsend);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	if (OidIsValid(typeForm->typmodin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodin;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typmodin);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	if (OidIsValid(typeForm->typmodout))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typmodout;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typmodout);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	if (OidIsValid(typeForm->typanalyze))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = typeForm->typanalyze;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typanalyze);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
@@ -673,9 +656,7 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 	 */
 	if (OidIsValid(typeForm->typrelid))
 	{
-		referenced.classId = RelationRelationId;
-		referenced.objectId = typeForm->typrelid;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, RelationRelationId, typeForm->typrelid);
 
 		if (relationKind != RELKIND_COMPOSITE_TYPE)
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
@@ -690,9 +671,7 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 	 */
 	if (OidIsValid(typeForm->typelem))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = typeForm->typelem;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, TypeRelationId, typeForm->typelem);
 		recordDependencyOn(&myself, &referenced,
 						   isImplicitArray ? DEPENDENCY_INTERNAL : DEPENDENCY_NORMAL);
 	}
@@ -700,9 +679,7 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 	/* Normal dependency from a domain to its base type. */
 	if (OidIsValid(typeForm->typbasetype))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = typeForm->typbasetype;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, TypeRelationId, typeForm->typbasetype);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
@@ -711,9 +688,7 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 	if (OidIsValid(typeForm->typcollation) &&
 		typeForm->typcollation != DEFAULT_COLLATION_OID)
 	{
-		referenced.classId = CollationRelationId;
-		referenced.objectId = typeForm->typcollation;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, CollationRelationId, typeForm->typcollation);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 472e69fdaf..d3b76adf5a 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1782,7 +1782,8 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	bool		nulls[Natts_pg_extension];
 	HeapTuple	tuple;
 	ObjectAddress myself;
-	ObjectAddress nsp;
+	ObjectAddresses *refobjs;
+	ObjectAddress obj;
 	ListCell   *lc;
 
 	/*
@@ -1823,29 +1824,29 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	/*
 	 * Record dependencies on owner, schema, and prerequisite extensions
 	 */
+	refobjs = new_object_addresses();
+
 	recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
 
 	myself.classId = ExtensionRelationId;
 	myself.objectId = extensionOid;
 	myself.objectSubId = 0;
 
-	nsp.classId = NamespaceRelationId;
-	nsp.objectId = schemaOid;
-	nsp.objectSubId = 0;
-
-	recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+	ObjectAddressSet(obj, NamespaceRelationId, schemaOid);
+	add_exact_object_address(&obj, refobjs);
 
 	foreach(lc, requiredExtensions)
 	{
 		Oid			reqext = lfirst_oid(lc);
-		ObjectAddress otherext;
+		ObjectAddress reqobj;
 
-		otherext.classId = ExtensionRelationId;
-		otherext.objectId = reqext;
-		otherext.objectSubId = 0;
-
-		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+		ObjectAddressSet(reqobj, ExtensionRelationId, reqext);
+		add_exact_object_address(&reqobj, refobjs);
 	}
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f39f..e7d8c062dd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5975,6 +5975,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	AlterTableCmd *childcmd;
 	AclResult	aclresult;
 	ObjectAddress address;
+	TupleDesc	tupdesc;
+	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -6128,11 +6130,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
-	/* attribute.attacl is handled by InsertPgAttributeTuple */
+	/* attribute.attacl is handled by InsertPgAttributeTuples */
 
 	ReleaseSysCache(typeTuple);
 
-	InsertPgAttributeTuple(attrdesc, &attribute, (Datum) 0, NULL);
+	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index e3e6634d7e..814416d936 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2069,11 +2069,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2097,11 +2096,10 @@ WHERE classid = 'pg_class'::regclass AND
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
- index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(9 rows)
+(8 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
#36Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#35)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 26 Jun 2020, at 10:11, Michael Paquier <michael@paquier.xyz> wrote:

On Thu, Jun 25, 2020 at 09:38:23AM +0200, Daniel Gustafsson wrote:

Attached is a rebased version which was updated to handle the changes for op
class parameters introduced in 911e70207703799605.

Thanks for the updated version.

Thanks for reviewing!

While re-reading the code, I got cold feet with the changes done in
recordDependencyOn(). Sure, we could do as you propose, but that does
not have to be part of this patch I think, aimed at switching more
catalogs to use multi inserts, and it just feels a duplicate of
recordMultipleDependencies(), with the same comments copied all over
the place, etc.

Fair enough, I can take that to another patch later in the cycle.

MAX_TEMPLATE_BYTES in pg_shdepend.c needs a comment to explain that
this is to cap the number of slots used in
copyTemplateDependencies() for pg_shdepend.

Agreed, +1 on the proposed wording.

Not much a fan of the changes in GenerateTypeDependencies(),
particularly the use of refobjs[8], capped to the number of items from
typeForm. If we add new members I think that this would break
easily without us actually noticing that it broke.

Yeah, thats not good, it's better to leave that out.

The use of
ObjectAddressSet() is a good idea though, but it does not feel
consistent if you don't the same coding rule to typbasetype,
typcollation or typelem. I am also thinking to split this part of the
cleanup in a first independent patch.

+1 on splitting into a separate patch.

pg_constraint.c, pg_operator.c, extension.c and pg_aggregate.c were
using ObjectAddressSubSet() with subset set to 0 when registering a
dependency. It is simpler to just use ObjectAddressSet().

Fair enough, either way, I don't have strong opinions.

As this
updates the way dependencies are tracked and recorded, that's better
if kept in the main patch.

Agreed.

+   /* TODO is nreferenced a reasonable allocation of slots? */
+   slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
It seems to me that we could just apply the same rule as for
pg_attribute and pg_shdepend, no?

I think so, I see no reason not to.

CatalogTupleInsertWithInfo() becomes mostly unused with this patch,
its only caller being now LOs. Just noticing, I'd rather not remove
it for now.

Agreed, let's not bite off that too here, there's enough to chew on.

The attached includes a bunch of modifications I have done while going
through the patch (I indend to split and apply the changes of
pg_type.c separately first, just lacked of time now to send a proper
split), and there is the number of slots for pg_depend insertions that
still needs to be addressed. On top of that pgindent has not been run
yet. That's all I have for today, overall the patch is taking a
committable shape :)

I like it, thanks for hacking on it. I will take another look at it later
today when back at my laptop.

cheers ./daniel

#37Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#36)
3 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Fri, Jun 26, 2020 at 02:26:50PM +0200, Daniel Gustafsson wrote:

On 26 Jun 2020, at 10:11, Michael Paquier <michael@paquier.xyz> wrote:

+   /* TODO is nreferenced a reasonable allocation of slots? */
+   slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
It seems to me that we could just apply the same rule as for
pg_attribute and pg_shdepend, no?

I think so, I see no reason not to.

I was actually looking a patch to potentially support REINDEX for
partitioned table, and the CONCURRENTLY case may need this patch,
still if a lot of dependencies are switched at once it may be a
problem, so it is better to cap it. One thing though is that we may
finish by allocating more slots than what's necessary if some
dependencies are pinned, but using multi-inserts would be a gain
anyway, and the patch does not insert in more slots than needed.

I like it, thanks for hacking on it. I will take another look at it later
today when back at my laptop.

Thanks. I have been able to apply the independent part of pg_type.c
as of 68de144.

Attached is a rebased set, with more splitting work done after a new
round of review. 0001 is more refactoring to use more
ObjectAddress[Sub]Set() where we can, leading to some more cleanup:
5 files changed, 43 insertions(+), 120 deletions(-)

In this round, I got confused with the way ObjectAddress items are
assigned, assuming that we have to use the same dependency type for a
bunch of dependencies to attach. Using add_exact_object_address() is
fine for this purpose, but this also makes me wonder if we should try
to think harder about this interface so as we would be able to insert
a bunch of dependency tuples with multiple types of dependencies
handled. So this has made me remove reset_object_addresses() from the
patch series, as it was used when dependency types were mixed up. We
could also discuss that separately, but that's not strongly mandatory
here.

There are however cases where it is straight-forward to gather
and insert multiple records, like in InsertExtensionTuple() (as does
already tsearchcmds.c), which is what 0002 does. An opposite example
is StorePartitionKey(), where there is a mix of normal and internal
dependencies, so I have removed it for now.

0003 is the main patch, where I have capped the number of slots used
for pg_depend similarly what is done for pg_shdepend and
pg_attribute to flush tuples in batches of 64kB.
ExecDropSingleTupleTableSlot() was also called for an incorrect number
of slots when it came to pg_shdepend. I was thinking if it could be
possible to do more consolidation between the three places where we
calculate the number of slots to use, but that would also mean to have
more tuple slot dependency moving around, which is not great. Anyway,
this leaves in patch 0003 only the multi-INSERT logic, without the
pieces that manipulated the dependency recording and insertions (we
still have three ObjectAddress[Sub]Set calls in heap.c but the same
area of the code is reworked for attribute insertions).

0001 and 0002 are committable and independent pieces, while 0003 still
needs more attention. One thing I also wanted to do with 0003 is
measure the difference in WAL produced (say pg_shdepend when using the
regression database as template) to have an idea of the gain.
--
Michael

Attachments:

0001-Refactor-ObjectAddress-field-assignments-in-more-pla.patchtext/x-diff; charset=us-asciiDownload
From 9552ba259e42d2d14e49aa19a86d27e1ebfd7cd3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 29 Jun 2020 14:52:58 +0900
Subject: [PATCH 1/3] Refactor ObjectAddress field assignments in more places

This is a continuation of 68de144, with more places in the backend code
that can be simplified with the macros able to assign values to the
fields of ObjectAddress.

Author: Daniel Gustafsson, Michael Paquier
Discussion: https://postgr.es/m/20190213182737.mxn6hkdxwrzgxk35@alap3.anarazel.de
---
 src/backend/catalog/index.c         | 39 ++++++++---------------------
 src/backend/catalog/pg_aggregate.c  | 36 +++++++-------------------
 src/backend/catalog/pg_constraint.c | 28 ++++++---------------
 src/backend/catalog/pg_operator.c   | 28 ++++++---------------
 src/backend/catalog/pg_proc.c       | 32 ++++++-----------------
 5 files changed, 43 insertions(+), 120 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index cdc01c49c9..fc088d3f52 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1030,9 +1030,7 @@ index_create(Relation heapRelation,
 		ObjectAddress myself,
 					referenced;
 
-		myself.classId = RelationRelationId;
-		myself.objectId = indexRelationId;
-		myself.objectSubId = 0;
+		ObjectAddressSet(myself, RelationRelationId, indexRelationId);
 
 		if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
 		{
@@ -1072,12 +1070,10 @@ index_create(Relation heapRelation,
 			{
 				if (indexInfo->ii_IndexAttrNumbers[i] != 0)
 				{
-					referenced.classId = RelationRelationId;
-					referenced.objectId = heapRelationId;
-					referenced.objectSubId = indexInfo->ii_IndexAttrNumbers[i];
-
+					ObjectAddressSubSet(referenced, RelationRelationId,
+										heapRelationId,
+										indexInfo->ii_IndexAttrNumbers[i]);
 					recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
-
 					have_simple_col = true;
 				}
 			}
@@ -1090,10 +1086,8 @@ index_create(Relation heapRelation,
 			 */
 			if (!have_simple_col)
 			{
-				referenced.classId = RelationRelationId;
-				referenced.objectId = heapRelationId;
-				referenced.objectSubId = 0;
-
+				ObjectAddressSet(referenced, RelationRelationId,
+								 heapRelationId);
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
 		}
@@ -1106,16 +1100,10 @@ index_create(Relation heapRelation,
 		 */
 		if (OidIsValid(parentIndexRelid))
 		{
-			referenced.classId = RelationRelationId;
-			referenced.objectId = parentIndexRelid;
-			referenced.objectSubId = 0;
-
+			ObjectAddressSet(referenced, RelationRelationId, parentIndexRelid);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI);
 
-			referenced.classId = RelationRelationId;
-			referenced.objectId = heapRelationId;
-			referenced.objectSubId = 0;
-
+			ObjectAddressSet(referenced, RelationRelationId, heapRelationId);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 		}
 
@@ -1126,10 +1114,8 @@ index_create(Relation heapRelation,
 			if (OidIsValid(collationObjectId[i]) &&
 				collationObjectId[i] != DEFAULT_COLLATION_OID)
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
-
+				ObjectAddressSet(referenced, CollationRelationId,
+								 collationObjectId[i]);
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 			}
 		}
@@ -1137,10 +1123,7 @@ index_create(Relation heapRelation,
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
-
+			ObjectAddressSet(referenced, OperatorClassRelationId, classObjectId[i]);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 7d887ea24a..89007ad1ed 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -742,80 +742,62 @@ AggregateCreate(const char *aggName,
 	 */
 
 	/* Depends on transition function */
-	referenced.classId = ProcedureRelationId;
-	referenced.objectId = transfn;
-	referenced.objectSubId = 0;
+	ObjectAddressSet(referenced, ProcedureRelationId, transfn);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = finalfn;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, finalfn);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Depends on combine function, if any */
 	if (OidIsValid(combinefn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = combinefn;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, combinefn);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Depends on serialization function, if any */
 	if (OidIsValid(serialfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = serialfn;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, serialfn);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Depends on deserialization function, if any */
 	if (OidIsValid(deserialfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = deserialfn;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, deserialfn);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Depends on forward transition function, if any */
 	if (OidIsValid(mtransfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mtransfn;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, mtransfn);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Depends on inverse transition function, if any */
 	if (OidIsValid(minvtransfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = minvtransfn;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, minvtransfn);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Depends on final function, if any */
 	if (OidIsValid(mfinalfn))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = mfinalfn;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, mfinalfn);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Depends on sort operator, if any */
 	if (OidIsValid(sortop))
 	{
-		referenced.classId = OperatorRelationId;
-		referenced.objectId = sortop;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, OperatorRelationId, sortop);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 90932be831..4f5b9e9f48 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -237,21 +237,18 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		ObjectAddress relobject;
 
-		relobject.classId = RelationRelationId;
-		relobject.objectId = relId;
 		if (constraintNTotalKeys > 0)
 		{
 			for (i = 0; i < constraintNTotalKeys; i++)
 			{
-				relobject.objectSubId = constraintKey[i];
-
+				ObjectAddressSubSet(relobject, RelationRelationId, relId,
+									constraintKey[i]);
 				recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
 			}
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
+			ObjectAddressSet(relobject, RelationRelationId, relId);
 			recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
 		}
 	}
@@ -263,10 +260,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		ObjectAddress domobject;
 
-		domobject.classId = TypeRelationId;
-		domobject.objectId = domainId;
-		domobject.objectSubId = 0;
-
+		ObjectAddressSet(domobject, TypeRelationId, domainId);
 		recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO);
 	}
 
@@ -278,21 +272,18 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		ObjectAddress relobject;
 
-		relobject.classId = RelationRelationId;
-		relobject.objectId = foreignRelId;
 		if (foreignNKeys > 0)
 		{
 			for (i = 0; i < foreignNKeys; i++)
 			{
-				relobject.objectSubId = foreignKey[i];
-
+				ObjectAddressSubSet(relobject, RelationRelationId,
+									foreignRelId, foreignKey[i]);
 				recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
 			}
 		}
 		else
 		{
-			relobject.objectSubId = 0;
-
+			ObjectAddressSet(relobject, RelationRelationId, foreignRelId);
 			recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
 		}
 	}
@@ -307,10 +298,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		ObjectAddress relobject;
 
-		relobject.classId = RelationRelationId;
-		relobject.objectId = indexRelId;
-		relobject.objectSubId = 0;
-
+		ObjectAddressSet(relobject, RelationRelationId, indexRelId);
 		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
 	}
 
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 340e284ae4..61254c8ba2 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -793,36 +793,28 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
 	{
-		referenced.classId = NamespaceRelationId;
-		referenced.objectId = oper->oprnamespace;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, NamespaceRelationId, oper->oprnamespace);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Dependency on left type */
 	if (OidIsValid(oper->oprleft))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprleft;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, TypeRelationId, oper->oprleft);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Dependency on right type */
 	if (OidIsValid(oper->oprright))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprright;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, TypeRelationId, oper->oprright);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Dependency on result type */
 	if (OidIsValid(oper->oprresult))
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = oper->oprresult;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, TypeRelationId, oper->oprresult);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
@@ -838,27 +830,21 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
 	/* Dependency on implementation function */
 	if (OidIsValid(oper->oprcode))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprcode;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, oper->oprcode);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Dependency on restriction selectivity function */
 	if (OidIsValid(oper->oprrest))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprrest;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, oper->oprrest);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* Dependency on join selectivity function */
 	if (OidIsValid(oper->oprjoin))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = oper->oprjoin;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, oper->oprjoin);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 6cdda35d1c..a28ab74d60 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -585,51 +585,37 @@ ProcedureCreate(const char *procedureName,
 	if (is_update)
 		deleteDependencyRecordsFor(ProcedureRelationId, retval, true);
 
-	myself.classId = ProcedureRelationId;
-	myself.objectId = retval;
-	myself.objectSubId = 0;
+	ObjectAddressSet(myself, ProcedureRelationId, retval);
 
 	/* dependency on namespace */
-	referenced.classId = NamespaceRelationId;
-	referenced.objectId = procNamespace;
-	referenced.objectSubId = 0;
+	ObjectAddressSet(referenced, NamespaceRelationId, procNamespace);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* dependency on implementation language */
-	referenced.classId = LanguageRelationId;
-	referenced.objectId = languageObjectId;
-	referenced.objectSubId = 0;
+	ObjectAddressSet(referenced, LanguageRelationId, languageObjectId);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* dependency on return type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = returnType;
-	referenced.objectSubId = 0;
+	ObjectAddressSet(referenced, TypeRelationId, returnType);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
 	{
-		referenced.classId = TransformRelationId;
-		referenced.objectId = trfid;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, TransformRelationId, trfid);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
 	/* dependency on parameter types */
 	for (i = 0; i < allParamCount; i++)
 	{
-		referenced.classId = TypeRelationId;
-		referenced.objectId = allParams[i];
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, TypeRelationId, allParams[i]);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
 		{
-			referenced.classId = TransformRelationId;
-			referenced.objectId = trfid;
-			referenced.objectSubId = 0;
+			ObjectAddressSet(referenced, TransformRelationId, trfid);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 	}
@@ -642,9 +628,7 @@ ProcedureCreate(const char *procedureName,
 	/* dependency on support function, if any */
 	if (OidIsValid(prosupport))
 	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = prosupport;
-		referenced.objectSubId = 0;
+		ObjectAddressSet(referenced, ProcedureRelationId, prosupport);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
-- 
2.27.0

0002-Refactor-dependency-handling-when-creating-extension.patchtext/x-diff; charset=us-asciiDownload
From efdd3f918a015d9e7ce8aafa4a17fe98033222c5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 29 Jun 2020 15:25:16 +0900
Subject: [PATCH 2/3] Refactor dependency handling when creating extension

Extensions mostly use the same type of dependencies when creating them,
so refactor the code to register all dependencies at once, and insert
them in batches.
---
 src/backend/commands/extension.c | 23 +++++++++++------------
 1 file changed, 11 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 472e69fdaf..5159e18cbf 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1783,6 +1783,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	HeapTuple	tuple;
 	ObjectAddress myself;
 	ObjectAddress nsp;
+	ObjectAddresses *refobjs;
 	ListCell   *lc;
 
 	/*
@@ -1825,27 +1826,25 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	 */
 	recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
 
-	myself.classId = ExtensionRelationId;
-	myself.objectId = extensionOid;
-	myself.objectSubId = 0;
+	refobjs = new_object_addresses();
 
-	nsp.classId = NamespaceRelationId;
-	nsp.objectId = schemaOid;
-	nsp.objectSubId = 0;
+	ObjectAddressSet(myself, ExtensionRelationId, extensionOid);
 
-	recordDependencyOn(&myself, &nsp, DEPENDENCY_NORMAL);
+	ObjectAddressSet(nsp, NamespaceRelationId, schemaOid);
+	add_exact_object_address(&nsp, refobjs);
 
 	foreach(lc, requiredExtensions)
 	{
 		Oid			reqext = lfirst_oid(lc);
 		ObjectAddress otherext;
 
-		otherext.classId = ExtensionRelationId;
-		otherext.objectId = reqext;
-		otherext.objectSubId = 0;
-
-		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
+		ObjectAddressSet(otherext, ExtensionRelationId, reqext);
+		add_exact_object_address(&otherext, refobjs);
 	}
+
+	record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
+	free_object_addresses(refobjs);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
-- 
2.27.0

0003-Do-multi-INSERTs-for-pg_depend-pg_shdepend-and-pg_at.patchtext/x-diff; charset=us-asciiDownload
From 804c7ce410ebb5e92a260d6c17dbaff9b3276070 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 29 Jun 2020 15:35:47 +0900
Subject: [PATCH 3/3] Do multi-INSERTs for pg_depend, pg_shdepend and
 pg_attribute

---
 src/include/catalog/heap.h        |   9 +-
 src/include/catalog/indexing.h    |   5 +
 src/backend/access/heap/heapam.c  |   4 -
 src/backend/catalog/heap.c        | 203 +++++++++++++++++-------------
 src/backend/catalog/index.c       |  19 +--
 src/backend/catalog/indexing.c    |  36 ++++++
 src/backend/catalog/pg_depend.c   |  96 ++++++++++----
 src/backend/catalog/pg_shdepend.c |  60 ++++++---
 src/backend/commands/tablecmds.c  |   8 +-
 9 files changed, 289 insertions(+), 151 deletions(-)

diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2abe..d31141c1a2 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -93,10 +93,11 @@ extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
 extern List *heap_truncate_find_FKs(List *relationIds);
 
-extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
-								   Form_pg_attribute new_attribute,
-								   Datum attoptions,
-								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									Datum *attoptions,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..a7e2a9b26b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 537913d1bb..3334bef458 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2168,10 +2168,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			RelationPutHeapTuple(relation, buffer, heaptup, false);
 
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
 			if (needwal && need_cids)
 				log_heap_new_cid(relation, heaptup);
 		}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3c83fe6bab..36e85e929f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -710,70 +710,122 @@ CheckAttributeType(const char *attname,
 }
 
 /*
- * InsertPgAttributeTuple
- *		Construct and insert a new tuple in pg_attribute.
+ * Cap the maximum amount of bytes allocated for InsertPgAttributeTuples()
+ * slots.
+ */
+#define MAX_PGATTRIBUTE_INSERT_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert a set of tuples in pg_attribute.
  *
- * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl,
- * attfdwoptions and attmissingval are always initialized to NULL.
+ * Caller has already opened and locked pg_attribute. tupdesc contains the
+ * attributes to insert.  attcacheoff is always initialized to -1, attacl,
+ * attfdwoptions and attmissingval are always initialized to NULL.  attoptions
+ * must contain the same number of elements as tupdesc, or be NULL.
  *
  * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
  * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
  * this when inserting multiple attributes, because it's a tad more
  * expensive.)
+ *
+ * new_rel_oid is the relation OID assigned to the attributes inserted.
+ * If set to InvalidOid, the relation OID from tupdesc is used instead.
  */
 void
-InsertPgAttributeTuple(Relation pg_attribute_rel,
-					   Form_pg_attribute new_attribute,
-					   Datum attoptions,
-					   CatalogIndexState indstate)
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						Datum *attoptions,
+						CatalogIndexState indstate)
 {
-	Datum		values[Natts_pg_attribute];
-	bool		nulls[Natts_pg_attribute];
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	TupleDesc	td;
+	int			nslots;
+	int			natts = 0;
+	int			slotCount = 0;
+	bool		close_index = false;
 
-	/* This is a tad tedious, but way cleaner than what we used to do... */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
+	td = RelationGetDescr(pg_attribute_rel);
 
-	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
-	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
-	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
-	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
-	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
-	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
-	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
-	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
-	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
-	values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
-	values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
-	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
-	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
-	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
-	values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
-	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
-	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
-	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
-	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
-	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
-	values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
-	values[Anum_pg_attribute_attoptions - 1] = attoptions;
+	/* Initialize the number of slots to use */
+	nslots = Min(tupdesc->natts,
+				 (MAX_PGATTRIBUTE_INSERT_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
 
-	/* start out with empty permissions and empty options */
-	nulls[Anum_pg_attribute_attacl - 1] = true;
-	nulls[Anum_pg_attribute_attoptions - 1] = attoptions == (Datum) 0;
-	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
-	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+	while (natts < tupdesc->natts)
+	{
+		Form_pg_attribute attrs = TupleDescAttr(tupdesc, natts);
 
-	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecClearTuple(slot[slotCount]);
 
-	/* finally insert the new tuple, update the indexes, and clean up */
-	if (indstate != NULL)
-		CatalogTupleInsertWithInfo(pg_attribute_rel, tup, indstate);
-	else
-		CatalogTupleInsert(pg_attribute_rel, tup);
+		if (new_rel_oid != InvalidOid)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(attrs->attrelid);
 
-	heap_freetuple(tup);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&attrs->attname);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(attrs->atttypid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(attrs->attstattarget);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(attrs->attlen);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attnum);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(attrs->attndims);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(attrs->atttypmod);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(attrs->attbyval);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(attrs->attstorage);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(attrs->attalign);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(attrs->attnotnull);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(attrs->atthasdef);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(attrs->atthasmissing);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(attrs->attidentity);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(attrs->attgenerated);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(attrs->attisdropped);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(attrs->attislocal);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(attrs->attinhcount);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(attrs->attcollation);
+		if (attoptions && attoptions[natts] != (Datum) 0)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attoptions - 1] = attoptions[natts];
+		else
+			slot[slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
+
+		/* start out with empty permissions and empty options */
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/*
+		 * If slots are full or the end of processing has been reached,
+		 * insert a batch of tuples.
+		 */
+		if (slotCount == nslots || natts == tupdesc->natts - 1)
+		{
+			/* fetch index info only when we know we need it */
+			if (!indstate)
+			{
+				indstate = CatalogOpenIndexes(pg_attribute_rel);
+				close_index = true;
+			}
+
+			/* insert the new tuples and update the indexes */
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, slotCount,
+											 indstate);
+			slotCount = 0;
+		}
+
+		natts++;
+	}
+
+	if (close_index)
+		CatalogCloseIndexes(indstate);
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /* --------------------------------
@@ -788,8 +840,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -803,36 +853,26 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
-	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
-	 */
-	for (i = 0; i < natts; i++)
+	/* set stats detail level to a sane default */
+	for (int i = 0; i < natts; i++)
+		tupdesc->attrs[i].attstattarget = -1;
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate);
+
+	/* add dependencies on their datatypes and collations */
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, (Datum) 0, indstate);
-
 		/* Add dependency info */
-		myself.classId = RelationRelationId;
-		myself.objectId = new_rel_oid;
-		myself.objectSubId = i + 1;
-		referenced.classId = TypeRelationId;
-		referenced.objectId = attr->atttypid;
-		referenced.objectSubId = 0;
+		ObjectAddressSubSet(myself, RelationRelationId, new_rel_oid, i + 1);
+		ObjectAddressSet(referenced, TypeRelationId,
+						 tupdesc->attrs[i].atttypid);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
-			referenced.classId = CollationRelationId;
-			referenced.objectId = attr->attcollation;
-			referenced.objectSubId = 0;
+			ObjectAddressSet(referenced, CollationRelationId,
+							 tupdesc->attrs[i].attcollation);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 	}
@@ -844,17 +884,12 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
+		TupleDesc	td;
 
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
+		td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, (Datum) 0, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index fc088d3f52..255ebac15b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -106,8 +106,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, int numatts,
-								  Datum *attopts);
+static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -485,12 +484,11 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
+AppendAttributeTuples(Relation indexRelation, Datum *attopts)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
 	TupleDesc	indexTupDesc;
-	int			i;
 
 	/*
 	 * open the attribute relation and its indexes
@@ -504,15 +502,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-		Datum		attoptions = attopts ? attopts[i] : (Datum) 0;
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, attoptions, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attopts, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -979,8 +969,7 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs,
-						  indexInfo->ii_OpclassOptions);
+	AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions);
 
 	/* ----------------
 	 *	  update pg_index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index d63fcf58cf..3d249b4cb5 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo - as above, but for multiple tuples
+ *
+ * Insert multiple tuples into the given catalog relation at once, with an
+ * amortized cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 21cfdcace9..1fe993d9a9 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -47,6 +47,12 @@ recordDependencyOn(const ObjectAddress *depender,
 	recordMultipleDependencies(depender, referenced, 1, behavior);
 }
 
+/*
+ * Cap the maximum amount of bytes allocated for recordMultipleDependencies()
+ * slots.
+ */
+#define MAX_PGDEPEND_INSERT_BYTES 65535
+
 /*
  * Record multiple dependencies (of the same kind) for a single dependent
  * object.  This has a little less overhead than recording each separately.
@@ -59,10 +65,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
-	int			i;
-	bool		nulls[Natts_pg_depend];
-	Datum		values[Natts_pg_depend];
+	TupleTableSlot **slot;
+	int			nslots;
+	int			slotCount = 0;
+	bool		close_index = false;
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -79,46 +85,82 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
+	/* Initialize the number of slots to use */
+	nslots = Min(nreferenced,
+				 (MAX_PGDEPEND_INSERT_BYTES / sizeof(FormData_pg_depend)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+										   &TTSOpsHeapTuple);
+
+	for (int i = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
 		 * need to record dependencies on it.  This saves lots of space in
 		 * pg_depend, so it's worth the time taken to check.
 		 */
-		if (!isObjectPinned(referenced, dependDesc))
+		if (isObjectPinned(referenced, dependDesc))
+			continue;
+
+		ExecClearTuple(slot[slotCount]);
+
+		/*
+		 * Record the Dependency.  Note we don't bother to check for
+		 * duplicate dependencies; there's no harm in them.
+		 */
+		slot[slotCount]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+		slot[slotCount]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+		slot[slotCount]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+		slot[slotCount]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+		slot[slotCount]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+		slot[slotCount]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+		slot[slotCount]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+		memset(slot[slotCount]->tts_isnull, false,
+			   slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/* If slots are full, insert a batch of tuples */
+		if (slotCount == nslots)
 		{
-			/*
-			 * Record the Dependency.  Note we don't bother to check for
-			 * duplicate dependencies; there's no harm in them.
-			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
-
-			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
-			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
-			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
-
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
-
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
-
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
+			{
 				indstate = CatalogOpenIndexes(dependDesc);
+				close_index = true;
+			}
 
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
+			/* Insert the new tuples and update the indexes */
+			CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slotCount,
+											 indstate);
+			slotCount = 0;
 		}
 	}
 
-	if (indstate != NULL)
+	/* Insert any tuples if any left in the buffer */
+	if (slotCount > 0)
+	{
+		/* Again, index information may not exist yet */
+		if (indstate == NULL)
+		{
+			indstate = CatalogOpenIndexes(dependDesc);
+			close_index = true;
+		}
+
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slotCount, indstate);
+	}
+
+	/* close the indexes, we are done */
+	if (close_index)
 		CatalogCloseIndexes(indstate);
 
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+
 	table_close(dependDesc, RowExclusiveLock);
 }
 
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index f776e821b3..1754bbfb57 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -785,6 +785,13 @@ checkSharedDependencies(Oid classId, Oid objectId,
 	return true;
 }
 
+
+/*
+ * Cap the maximum amount of bytes allocated for copyTemplateDependencies()
+ * slots.
+ */
+#define MAX_PGSHDEPEND_INSERT_BYTES 65535
+
 /*
  * copyTemplateDependencies
  *
@@ -799,14 +806,19 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	int			slotCount;
 	CatalogIndexState indstate;
-	Datum		values[Natts_pg_shdepend];
-	bool		nulls[Natts_pg_shdepend];
-	bool		replace[Natts_pg_shdepend];
+	TupleTableSlot **slot;
+	int			nslots;
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
 
+	nslots = MAX_PGSHDEPEND_INSERT_BYTES / sizeof(FormData_pg_shdepend);
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+
 	indstate = CatalogOpenIndexes(sdepRel);
 
 	/* Scan all entries with dbid = templateDbId */
@@ -818,14 +830,6 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
 							  NULL, 1, key);
 
-	/* Set up to copy the tuples except for inserting newDbId */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
-	memset(replace, false, sizeof(replace));
-
-	replace[Anum_pg_shdepend_dbid - 1] = true;
-	values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(newDbId);
-
 	/*
 	 * Copy the entries of the original database, changing the database Id to
 	 * that of the new database.  Note that because we are not copying rows
@@ -833,20 +837,46 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	slotCount = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
+		Form_pg_shdepend shdep;
 
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		ExecClearTuple(slot[slotCount]);
 
-		heap_freetuple(newtup);
+		shdep = (Form_pg_shdepend) GETSTRUCT(tup);
+
+		slot[slotCount]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId);
+		slot[slotCount]->tts_values[Anum_pg_shdepend_classid] = shdep->classid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objid] = shdep->objid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/* If slots are full, insert a batch of tuples */
+		if (slotCount == nslots)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (slotCount > 0)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
 	table_close(sdepRel, RowExclusiveLock);
+
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f39f..e7d8c062dd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5975,6 +5975,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	AlterTableCmd *childcmd;
 	AclResult	aclresult;
 	ObjectAddress address;
+	TupleDesc	tupdesc;
+	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -6128,11 +6130,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
-	/* attribute.attacl is handled by InsertPgAttributeTuple */
+	/* attribute.attacl is handled by InsertPgAttributeTuples */
 
 	ReleaseSysCache(typeTuple);
 
-	InsertPgAttributeTuple(attrdesc, &attribute, (Datum) 0, NULL);
+	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
-- 
2.27.0

#38Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#37)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 29 Jun 2020, at 08:57, Michael Paquier <michael@paquier.xyz> wrote:

On Fri, Jun 26, 2020 at 02:26:50PM +0200, Daniel Gustafsson wrote:

On 26 Jun 2020, at 10:11, Michael Paquier <michael@paquier.xyz> wrote:

+   /* TODO is nreferenced a reasonable allocation of slots? */
+   slot = palloc(sizeof(TupleTableSlot *) * nreferenced);
It seems to me that we could just apply the same rule as for
pg_attribute and pg_shdepend, no?

I think so, I see no reason not to.

I was actually looking a patch to potentially support REINDEX for
partitioned table, and the CONCURRENTLY case may need this patch,
still if a lot of dependencies are switched at once it may be a
problem, so it is better to cap it.

Agreed.

One thing though is that we may finish by allocating more slots than what's
necessary if some dependencies are pinned, but using multi-inserts would be a
gain anyway, and the patch does not insert in more slots than needed.

I was playing around with a few approaches around this when I initially wrote
this, but I was unable to find any way to calculate the correct number of slots
which wasn't significantly more expensive than the extra allocations.

Attached is a rebased set, with more splitting work done after a new
round of review. 0001 is more refactoring to use more
ObjectAddress[Sub]Set() where we can, leading to some more cleanup:
5 files changed, 43 insertions(+), 120 deletions(-)

This patch seems quite straightforward, and in the case of the loops in for
example CreateConstraintEntry() makes the code a lot more readable. +1 for
applying 0001.

In this round, I got confused with the way ObjectAddress items are
assigned, assuming that we have to use the same dependency type for a
bunch of dependencies to attach. Using add_exact_object_address() is
fine for this purpose, but this also makes me wonder if we should try
to think harder about this interface so as we would be able to insert
a bunch of dependency tuples with multiple types of dependencies
handled. So this has made me remove reset_object_addresses() from the
patch series, as it was used when dependency types were mixed up. We
could also discuss that separately, but that's not strongly mandatory
here.

Ok, once the final state of this patchset is known I can take a stab at
recording multiple dependencies with different behaviors as a separate
patchset.

There are however cases where it is straight-forward to gather and insert
multiple records, like in InsertExtensionTuple() (as does already
tsearchcmds.c), which is what 0002 does.

+1 on 0002 as well.

An opposite example is StorePartitionKey(), where there is a mix of normal and
internal dependencies, so I have removed it for now.

I don't think it makes the code any worse off to handle the NORMAL dependencies
separate here, but MVV.

ExecDropSingleTupleTableSlot() was also called for an incorrect number
of slots when it came to pg_shdepend.

Hmm, don't you mean in heap.c:InsertPgAttributeTuples()?

I was thinking if it could be possible to do more consolidation between the
three places where we calculate the number of slots to use, but that would also
mean to have more tuple slot dependency moving around, which is not great.

If we do, we need to keep the cap consistent across all callers, else we'll end
up with an API without an abstraction to make it worth more than saving a few
lines of quite simple to read code. Currently this is the case, but that might
not always hold, so not sure it if it's worth it.

0001 + 0002 + 0003 pass tests on my machine and nothing sticks out.

cheers ./daniel

#39Noname
michael@paquier.xyz
In reply to: Daniel Gustafsson (#38)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Tue, Jun 30, 2020 at 02:25:07PM +0200, Daniel Gustafsson wrote:

Ok, once the final state of this patchset is known I can take a stab at
recording multiple dependencies with different behaviors as a separate
patchset.

Thanks. I have applied 0001 and 0002 today, in a reversed order
actually.

If we do, we need to keep the cap consistent across all callers, else we'll end
up with an API without an abstraction to make it worth more than saving a few
lines of quite simple to read code. Currently this is the case, but that might
not always hold, so not sure it if it's worth it.

I am not sure either, still it looks worth thinking about it.
Attached is a rebased version of the last patch. What this currently
holds is just the switch to heap_multi_insert() for the three catalogs
pg_attribute, pg_depend and pg_shdepend. One point that looks worth
debating about is to how much to cap the data inserted at once. This
uses 64kB for all three, with a number of slots chosen based on the
size of each record, similarly to what we do for COPY.
--
Michael

Attachments:

catalog_multi_insert-v12.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2abe..d31141c1a2 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -93,10 +93,11 @@ extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
 extern List *heap_truncate_find_FKs(List *relationIds);
 
-extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
-								   Form_pg_attribute new_attribute,
-								   Datum attoptions,
-								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									Datum *attoptions,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..a7e2a9b26b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 537913d1bb..3334bef458 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2168,10 +2168,6 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			RelationPutHeapTuple(relation, buffer, heaptup, false);
 
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
 			if (needwal && need_cids)
 				log_heap_new_cid(relation, heaptup);
 		}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d279842d3c..36e85e929f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -710,70 +710,122 @@ CheckAttributeType(const char *attname,
 }
 
 /*
- * InsertPgAttributeTuple
- *		Construct and insert a new tuple in pg_attribute.
+ * Cap the maximum amount of bytes allocated for InsertPgAttributeTuples()
+ * slots.
+ */
+#define MAX_PGATTRIBUTE_INSERT_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert a set of tuples in pg_attribute.
  *
- * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl,
- * attfdwoptions and attmissingval are always initialized to NULL.
+ * Caller has already opened and locked pg_attribute. tupdesc contains the
+ * attributes to insert.  attcacheoff is always initialized to -1, attacl,
+ * attfdwoptions and attmissingval are always initialized to NULL.  attoptions
+ * must contain the same number of elements as tupdesc, or be NULL.
  *
  * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
  * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
  * this when inserting multiple attributes, because it's a tad more
  * expensive.)
+ *
+ * new_rel_oid is the relation OID assigned to the attributes inserted.
+ * If set to InvalidOid, the relation OID from tupdesc is used instead.
  */
 void
-InsertPgAttributeTuple(Relation pg_attribute_rel,
-					   Form_pg_attribute new_attribute,
-					   Datum attoptions,
-					   CatalogIndexState indstate)
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						Datum *attoptions,
+						CatalogIndexState indstate)
 {
-	Datum		values[Natts_pg_attribute];
-	bool		nulls[Natts_pg_attribute];
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	TupleDesc	td;
+	int			nslots;
+	int			natts = 0;
+	int			slotCount = 0;
+	bool		close_index = false;
 
-	/* This is a tad tedious, but way cleaner than what we used to do... */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
+	td = RelationGetDescr(pg_attribute_rel);
 
-	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
-	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
-	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
-	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
-	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
-	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
-	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
-	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
-	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
-	values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
-	values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
-	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
-	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
-	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
-	values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
-	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
-	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
-	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
-	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
-	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
-	values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
-	values[Anum_pg_attribute_attoptions - 1] = attoptions;
+	/* Initialize the number of slots to use */
+	nslots = Min(tupdesc->natts,
+				 (MAX_PGATTRIBUTE_INSERT_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
 
-	/* start out with empty permissions and empty options */
-	nulls[Anum_pg_attribute_attacl - 1] = true;
-	nulls[Anum_pg_attribute_attoptions - 1] = attoptions == (Datum) 0;
-	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
-	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+	while (natts < tupdesc->natts)
+	{
+		Form_pg_attribute attrs = TupleDescAttr(tupdesc, natts);
 
-	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecClearTuple(slot[slotCount]);
 
-	/* finally insert the new tuple, update the indexes, and clean up */
-	if (indstate != NULL)
-		CatalogTupleInsertWithInfo(pg_attribute_rel, tup, indstate);
-	else
-		CatalogTupleInsert(pg_attribute_rel, tup);
+		if (new_rel_oid != InvalidOid)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(attrs->attrelid);
 
-	heap_freetuple(tup);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&attrs->attname);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(attrs->atttypid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(attrs->attstattarget);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(attrs->attlen);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attnum);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(attrs->attndims);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(attrs->atttypmod);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(attrs->attbyval);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(attrs->attstorage);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(attrs->attalign);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(attrs->attnotnull);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(attrs->atthasdef);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(attrs->atthasmissing);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(attrs->attidentity);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(attrs->attgenerated);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(attrs->attisdropped);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(attrs->attislocal);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(attrs->attinhcount);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(attrs->attcollation);
+		if (attoptions && attoptions[natts] != (Datum) 0)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attoptions - 1] = attoptions[natts];
+		else
+			slot[slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
+
+		/* start out with empty permissions and empty options */
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/*
+		 * If slots are full or the end of processing has been reached,
+		 * insert a batch of tuples.
+		 */
+		if (slotCount == nslots || natts == tupdesc->natts - 1)
+		{
+			/* fetch index info only when we know we need it */
+			if (!indstate)
+			{
+				indstate = CatalogOpenIndexes(pg_attribute_rel);
+				close_index = true;
+			}
+
+			/* insert the new tuples and update the indexes */
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, slotCount,
+											 indstate);
+			slotCount = 0;
+		}
+
+		natts++;
+	}
+
+	if (close_index)
+		CatalogCloseIndexes(indstate);
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /* --------------------------------
@@ -788,8 +840,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -803,30 +853,26 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
-	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
-	 */
-	for (i = 0; i < natts; i++)
+	/* set stats detail level to a sane default */
+	for (int i = 0; i < natts; i++)
+		tupdesc->attrs[i].attstattarget = -1;
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate);
+
+	/* add dependencies on their datatypes and collations */
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, (Datum) 0, indstate);
-
 		/* Add dependency info */
 		ObjectAddressSubSet(myself, RelationRelationId, new_rel_oid, i + 1);
-		ObjectAddressSet(referenced, TypeRelationId, attr->atttypid);
+		ObjectAddressSet(referenced, TypeRelationId,
+						 tupdesc->attrs[i].atttypid);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
-			ObjectAddressSet(referenced, CollationRelationId, attr->attcollation);
+			ObjectAddressSet(referenced, CollationRelationId,
+							 tupdesc->attrs[i].attcollation);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 	}
@@ -838,17 +884,12 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
+		TupleDesc	td;
 
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
+		td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, (Datum) 0, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index fc088d3f52..255ebac15b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -106,8 +106,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, int numatts,
-								  Datum *attopts);
+static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -485,12 +484,11 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
+AppendAttributeTuples(Relation indexRelation, Datum *attopts)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
 	TupleDesc	indexTupDesc;
-	int			i;
 
 	/*
 	 * open the attribute relation and its indexes
@@ -504,15 +502,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-		Datum		attoptions = attopts ? attopts[i] : (Datum) 0;
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, attoptions, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attopts, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -979,8 +969,7 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs,
-						  indexInfo->ii_OpclassOptions);
+	AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions);
 
 	/* ----------------
 	 *	  update pg_index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index d63fcf58cf..3d249b4cb5 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -209,6 +210,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo - as above, but for multiple tuples
+ *
+ * Insert multiple tuples into the given catalog relation at once, with an
+ * amortized cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 21cfdcace9..1fe993d9a9 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -47,6 +47,12 @@ recordDependencyOn(const ObjectAddress *depender,
 	recordMultipleDependencies(depender, referenced, 1, behavior);
 }
 
+/*
+ * Cap the maximum amount of bytes allocated for recordMultipleDependencies()
+ * slots.
+ */
+#define MAX_PGDEPEND_INSERT_BYTES 65535
+
 /*
  * Record multiple dependencies (of the same kind) for a single dependent
  * object.  This has a little less overhead than recording each separately.
@@ -59,10 +65,10 @@ recordMultipleDependencies(const ObjectAddress *depender,
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
-	HeapTuple	tup;
-	int			i;
-	bool		nulls[Natts_pg_depend];
-	Datum		values[Natts_pg_depend];
+	TupleTableSlot **slot;
+	int			nslots;
+	int			slotCount = 0;
+	bool		close_index = false;
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -79,46 +85,82 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
+	/* Initialize the number of slots to use */
+	nslots = Min(nreferenced,
+				 (MAX_PGDEPEND_INSERT_BYTES / sizeof(FormData_pg_depend)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
 
-	for (i = 0; i < nreferenced; i++, referenced++)
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+										   &TTSOpsHeapTuple);
+
+	for (int i = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
 		 * If the referenced object is pinned by the system, there's no real
 		 * need to record dependencies on it.  This saves lots of space in
 		 * pg_depend, so it's worth the time taken to check.
 		 */
-		if (!isObjectPinned(referenced, dependDesc))
+		if (isObjectPinned(referenced, dependDesc))
+			continue;
+
+		ExecClearTuple(slot[slotCount]);
+
+		/*
+		 * Record the Dependency.  Note we don't bother to check for
+		 * duplicate dependencies; there's no harm in them.
+		 */
+		slot[slotCount]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+		slot[slotCount]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+		slot[slotCount]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+		slot[slotCount]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+		slot[slotCount]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+		slot[slotCount]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+		slot[slotCount]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
+
+		memset(slot[slotCount]->tts_isnull, false,
+			   slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/* If slots are full, insert a batch of tuples */
+		if (slotCount == nslots)
 		{
-			/*
-			 * Record the Dependency.  Note we don't bother to check for
-			 * duplicate dependencies; there's no harm in them.
-			 */
-			values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
-			values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
-			values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
-
-			values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
-			values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
-			values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
-
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
-
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
-
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
+			{
 				indstate = CatalogOpenIndexes(dependDesc);
+				close_index = true;
+			}
 
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
+			/* Insert the new tuples and update the indexes */
+			CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slotCount,
+											 indstate);
+			slotCount = 0;
 		}
 	}
 
-	if (indstate != NULL)
+	/* Insert any tuples if any left in the buffer */
+	if (slotCount > 0)
+	{
+		/* Again, index information may not exist yet */
+		if (indstate == NULL)
+		{
+			indstate = CatalogOpenIndexes(dependDesc);
+			close_index = true;
+		}
+
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slotCount, indstate);
+	}
+
+	/* close the indexes, we are done */
+	if (close_index)
 		CatalogCloseIndexes(indstate);
 
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+
 	table_close(dependDesc, RowExclusiveLock);
 }
 
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index f776e821b3..1754bbfb57 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -785,6 +785,13 @@ checkSharedDependencies(Oid classId, Oid objectId,
 	return true;
 }
 
+
+/*
+ * Cap the maximum amount of bytes allocated for copyTemplateDependencies()
+ * slots.
+ */
+#define MAX_PGSHDEPEND_INSERT_BYTES 65535
+
 /*
  * copyTemplateDependencies
  *
@@ -799,14 +806,19 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	int			slotCount;
 	CatalogIndexState indstate;
-	Datum		values[Natts_pg_shdepend];
-	bool		nulls[Natts_pg_shdepend];
-	bool		replace[Natts_pg_shdepend];
+	TupleTableSlot **slot;
+	int			nslots;
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
 
+	nslots = MAX_PGSHDEPEND_INSERT_BYTES / sizeof(FormData_pg_shdepend);
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+
 	indstate = CatalogOpenIndexes(sdepRel);
 
 	/* Scan all entries with dbid = templateDbId */
@@ -818,14 +830,6 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
 							  NULL, 1, key);
 
-	/* Set up to copy the tuples except for inserting newDbId */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
-	memset(replace, false, sizeof(replace));
-
-	replace[Anum_pg_shdepend_dbid - 1] = true;
-	values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(newDbId);
-
 	/*
 	 * Copy the entries of the original database, changing the database Id to
 	 * that of the new database.  Note that because we are not copying rows
@@ -833,20 +837,46 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	slotCount = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
+		Form_pg_shdepend shdep;
 
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		ExecClearTuple(slot[slotCount]);
 
-		heap_freetuple(newtup);
+		shdep = (Form_pg_shdepend) GETSTRUCT(tup);
+
+		slot[slotCount]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId);
+		slot[slotCount]->tts_values[Anum_pg_shdepend_classid] = shdep->classid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objid] = shdep->objid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/* If slots are full, insert a batch of tuples */
+		if (slotCount == nslots)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (slotCount > 0)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
 	table_close(sdepRel, RowExclusiveLock);
+
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f39f..e7d8c062dd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5975,6 +5975,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	AlterTableCmd *childcmd;
 	AclResult	aclresult;
 	ObjectAddress address;
+	TupleDesc	tupdesc;
+	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -6128,11 +6130,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
-	/* attribute.attacl is handled by InsertPgAttributeTuple */
+	/* attribute.attacl is handled by InsertPgAttributeTuples */
 
 	ReleaseSysCache(typeTuple);
 
-	InsertPgAttributeTuple(attrdesc, &attribute, (Datum) 0, NULL);
+	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
#40Michael Paquier
michael@paquier.xyz
In reply to: Noname (#39)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Wed, Jul 01, 2020 at 06:24:18PM +0900, Michael Paquier wrote:

I am not sure either, still it looks worth thinking about it.
Attached is a rebased version of the last patch. What this currently
holds is just the switch to heap_multi_insert() for the three catalogs
pg_attribute, pg_depend and pg_shdepend. One point that looks worth
debating about is to how much to cap the data inserted at once. This
uses 64kB for all three, with a number of slots chosen based on the
size of each record, similarly to what we do for COPY.

I got an extra round of review done for this patch. One spot was
missed in heap_multi_insert() for a comment telling catalogs not using
multi inserts. After some consideration, I think that using 64kB as a
base number to calculate the number of slots should be fine, similarly
to COPY.

While on it, I have done some measurements to see the difference in
WAL produced and get an idea of the gain. For example, this function
would create one table with a wanted number of attributes:
CREATE OR REPLACE FUNCTION create_cols(tabname text, num_cols int)
RETURNS VOID AS
$func$
DECLARE
query text;
BEGIN
query := 'CREATE TABLE ' || tabname || ' (';
FOR i IN 1..num_cols LOOP
query := query || 'a_' || i::text || ' int';
IF i != num_cols THEN
query := query || ', ';
END IF;
END LOOP;
query := query || ')';
EXECUTE format(query);
END
$func$ LANGUAGE plpgsql;

On HEAD, with a table that has 1300 attributes, this leads to 563kB of
WAL produced. With the patch, we get down to 505kB. That's an
extreme case of course, but that's nice a nice gain.

A second test, after creating a database from a template that has
roughly 10k entries in pg_shdepend (10k empty tables actually), showed
a reduction from 2158kB to 1762kB in WAL.

Finally comes the third catalog, pg_depend, and there is one thing
that makes me itching about this part. We do a lot of useless work
for the allocation and destruction of the slots when there are pinned
dependencies, and there can be a lot of them. Just by running the
main regression tests, it is easy to see that in 0003 we still do a
lot of calls of recordMultipleDependencies() for one single
dependency, and that most of these are actually pinned. So we finish
by doing a lot of slot manipulation to insert nothing at the end,
contrary to the counterparts with pg_shdepend and pg_attribute. In
short, I think that for now it would be fine to commit a patch that
does the multi-INSERT optimization for pg_attribute and pg_shdepend,
but that we need more careful work for pg_depend. For example we
could go through all the dependencies first and recalculate the number
of slots to use depending on what is pinned or not, but this would
make sense actually when more dependencies are inserted at once in
more code paths, mostly for ALTER TABLE. So this needs more
consideration IMO.

Thoughts?
--
Michael

#41Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#40)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 29 Jul 2020, at 10:32, Michael Paquier <michael@paquier.xyz> wrote:

On Wed, Jul 01, 2020 at 06:24:18PM +0900, Michael Paquier wrote:

I am not sure either, still it looks worth thinking about it.
Attached is a rebased version of the last patch. What this currently
holds is just the switch to heap_multi_insert() for the three catalogs
pg_attribute, pg_depend and pg_shdepend. One point that looks worth
debating about is to how much to cap the data inserted at once. This
uses 64kB for all three, with a number of slots chosen based on the
size of each record, similarly to what we do for COPY.

I got an extra round of review done for this patch.

Thanks!

While on it, I have done some measurements to see the difference in
WAL produced and get an idea of the gain.

On HEAD, with a table that has 1300 attributes, this leads to 563kB of
WAL produced. With the patch, we get down to 505kB. That's an
extreme case of course, but that's nice a nice gain.

A second test, after creating a database from a template that has
roughly 10k entries in pg_shdepend (10k empty tables actually), showed
a reduction from 2158kB to 1762kB in WAL.

Extreme cases for sure, but more importantly, there should be no cases when
this would cause an increase wrt the status quo.

Finally comes the third catalog, pg_depend, and there is one thing
that makes me itching about this part. We do a lot of useless work
for the allocation and destruction of the slots when there are pinned
dependencies, and there can be a lot of them. Just by running the
main regression tests, it is easy to see that in 0003 we still do a
lot of calls of recordMultipleDependencies() for one single
dependency, and that most of these are actually pinned. So we finish
by doing a lot of slot manipulation to insert nothing at the end,
contrary to the counterparts with pg_shdepend and pg_attribute.

Maybe it'd be worth pre-computing by a first pass which tracks pinned objects
in a bitmap; with a second pass which then knows how many and which to insert
into slots?

In
short, I think that for now it would be fine to commit a patch that
does the multi-INSERT optimization for pg_attribute and pg_shdepend,
but that we need more careful work for pg_depend.

Fair enough, let's break out pg_depend and I'll have another go at that.

cheers ./daniel

#42Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#41)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Wed, Jul 29, 2020 at 11:34:07PM +0200, Daniel Gustafsson wrote:

Extreme cases for sure, but more importantly, there should be no cases when
this would cause an increase wrt the status quo.

Yep.

Maybe it'd be worth pre-computing by a first pass which tracks pinned objects
in a bitmap; with a second pass which then knows how many and which to insert
into slots?

Or it could be possible to just rebuild a new list of dependencies
before insertion into the catalog. No objections with a bitmap, any
approach would be fine here as long as there is a first pass on the
item list.

Fair enough, let's break out pg_depend and I'll have another go at that.

Thanks. Attached is a polished version of the patch that I intend to
commit for pg_attribute and pg_shdepend. Let's look again at
pg_depend later, as there are also links with the handling of
dependencies for ALTER TABLE mainly.
--
Michael

Attachments:

catalog_multi_insert-v13.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2abe..d31141c1a2 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -93,10 +93,11 @@ extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
 extern List *heap_truncate_find_FKs(List *relationIds);
 
-extern void InsertPgAttributeTuple(Relation pg_attribute_rel,
-								   Form_pg_attribute new_attribute,
-								   Datum attoptions,
-								   CatalogIndexState indstate);
+extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
+									TupleDesc tupdesc,
+									Oid new_rel_oid,
+									Datum *attoptions,
+									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
 							   Relation new_rel_desc,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..a7e2a9b26b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -19,6 +19,7 @@
 #define INDEXING_H
 
 #include "access/htup.h"
+#include "nodes/execnodes.h"
 #include "utils/relcache.h"
 
 /*
@@ -36,6 +37,10 @@ extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
 extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 									   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
+											 TupleTableSlot **slot,
+											 int ntuples,
+											 CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, ItemPointer otid,
 							   HeapTuple tup);
 extern void CatalogTupleUpdateWithInfo(Relation heapRel,
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8df2716de4..5eef225f5c 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2164,8 +2164,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
 
 		/*
-		 * Note that heap_multi_insert is not used for catalog tuples yet, but
-		 * this will cover the gap once that is the case.
+		 * For logical decoding we need combocids to properly decode the
+		 * catalog.
 		 */
 		if (needwal && need_cids)
 			log_heap_new_cid(relation, heaptuples[ndone]);
@@ -2180,8 +2180,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 			RelationPutHeapTuple(relation, buffer, heaptup, false);
 
 			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
+			 * For logical decoding we need combocids to properly decode the
+			 * catalog.
 			 */
 			if (needwal && need_cids)
 				log_heap_new_cid(relation, heaptup);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3985326df6..2a18dca34d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -710,70 +710,122 @@ CheckAttributeType(const char *attname,
 }
 
 /*
- * InsertPgAttributeTuple
- *		Construct and insert a new tuple in pg_attribute.
+ * Cap the maximum amount of bytes allocated for InsertPgAttributeTuples()
+ * slots.
+ */
+#define MAX_PGATTRIBUTE_INSERT_BYTES 65535
+
+/*
+ * InsertPgAttributeTuples
+ *		Construct and insert a set of tuples in pg_attribute.
  *
- * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.  attcacheoff is always initialized to -1, attacl,
- * attfdwoptions and attmissingval are always initialized to NULL.
+ * Caller has already opened and locked pg_attribute. tupdesc contains the
+ * attributes to insert.  attcacheoff is always initialized to -1, attacl,
+ * attfdwoptions and attmissingval are always initialized to NULL.  attoptions
+ * must contain the same number of elements as tupdesc, or be NULL.
  *
  * indstate is the index state for CatalogTupleInsertWithInfo.  It can be
  * passed as NULL, in which case we'll fetch the necessary info.  (Don't do
  * this when inserting multiple attributes, because it's a tad more
  * expensive.)
+ *
+ * new_rel_oid is the relation OID assigned to the attributes inserted.
+ * If set to InvalidOid, the relation OID from tupdesc is used instead.
  */
 void
-InsertPgAttributeTuple(Relation pg_attribute_rel,
-					   Form_pg_attribute new_attribute,
-					   Datum attoptions,
-					   CatalogIndexState indstate)
+InsertPgAttributeTuples(Relation pg_attribute_rel,
+						TupleDesc tupdesc,
+						Oid new_rel_oid,
+						Datum *attoptions,
+						CatalogIndexState indstate)
 {
-	Datum		values[Natts_pg_attribute];
-	bool		nulls[Natts_pg_attribute];
-	HeapTuple	tup;
+	TupleTableSlot **slot;
+	TupleDesc	td;
+	int			nslots;
+	int			natts = 0;
+	int			slotCount = 0;
+	bool		close_index = false;
 
-	/* This is a tad tedious, but way cleaner than what we used to do... */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
+	td = RelationGetDescr(pg_attribute_rel);
 
-	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
-	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
-	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
-	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
-	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
-	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
-	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
-	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
-	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
-	values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
-	values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
-	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
-	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
-	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
-	values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
-	values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
-	values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated);
-	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
-	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
-	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
-	values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
-	values[Anum_pg_attribute_attoptions - 1] = attoptions;
+	/* Initialize the number of slots to use */
+	nslots = Min(tupdesc->natts,
+				 (MAX_PGATTRIBUTE_INSERT_BYTES / sizeof(FormData_pg_attribute)));
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
 
-	/* start out with empty permissions and empty options */
-	nulls[Anum_pg_attribute_attacl - 1] = true;
-	nulls[Anum_pg_attribute_attoptions - 1] = attoptions == (Datum) 0;
-	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
-	nulls[Anum_pg_attribute_attmissingval - 1] = true;
+	while (natts < tupdesc->natts)
+	{
+		Form_pg_attribute attrs = TupleDescAttr(tupdesc, natts);
 
-	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
+		ExecClearTuple(slot[slotCount]);
 
-	/* finally insert the new tuple, update the indexes, and clean up */
-	if (indstate != NULL)
-		CatalogTupleInsertWithInfo(pg_attribute_rel, tup, indstate);
-	else
-		CatalogTupleInsert(pg_attribute_rel, tup);
+		if (new_rel_oid != InvalidOid)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_rel_oid);
+		else
+			slot[slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(attrs->attrelid);
 
-	heap_freetuple(tup);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&attrs->attname);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(attrs->atttypid);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(attrs->attstattarget);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(attrs->attlen);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attnum);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(attrs->attndims);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(attrs->atttypmod);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(attrs->attbyval);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(attrs->attstorage);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(attrs->attalign);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(attrs->attnotnull);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(attrs->atthasdef);
+		slot[slotCount]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(attrs->atthasmissing);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(attrs->attidentity);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(attrs->attgenerated);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(attrs->attisdropped);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(attrs->attislocal);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(attrs->attinhcount);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(attrs->attcollation);
+		if (attoptions && attoptions[natts] != (Datum) 0)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attoptions - 1] = attoptions[natts];
+		else
+			slot[slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
+
+		/* start out with empty permissions and empty options */
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
+		slot[slotCount]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/*
+		 * If slots are full or the end of processing has been reached, insert
+		 * a batch of tuples.
+		 */
+		if (slotCount == nslots || natts == tupdesc->natts - 1)
+		{
+			/* fetch index info only when we know we need it */
+			if (!indstate)
+			{
+				indstate = CatalogOpenIndexes(pg_attribute_rel);
+				close_index = true;
+			}
+
+			/* insert the new tuples and update the indexes */
+			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, slotCount,
+											 indstate);
+			slotCount = 0;
+		}
+
+		natts++;
+	}
+
+	if (close_index)
+		CatalogCloseIndexes(indstate);
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /* --------------------------------
@@ -788,8 +840,6 @@ AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
 					  char relkind)
 {
-	Form_pg_attribute attr;
-	int			i;
 	Relation	rel;
 	CatalogIndexState indstate;
 	int			natts = tupdesc->natts;
@@ -803,30 +853,26 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 	indstate = CatalogOpenIndexes(rel);
 
-	/*
-	 * First we add the user attributes.  This is also a convenient place to
-	 * add dependencies on their datatypes and collations.
-	 */
-	for (i = 0; i < natts; i++)
+	/* set stats detail level to a sane default */
+	for (int i = 0; i < natts; i++)
+		tupdesc->attrs[i].attstattarget = -1;
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate);
+
+	/* add dependencies on their datatypes and collations */
+	for (int i = 0; i < natts; i++)
 	{
-		attr = TupleDescAttr(tupdesc, i);
-		/* Fill in the correct relation OID */
-		attr->attrelid = new_rel_oid;
-		/* Make sure this is OK, too */
-		attr->attstattarget = -1;
-
-		InsertPgAttributeTuple(rel, attr, (Datum) 0, indstate);
-
 		/* Add dependency info */
 		ObjectAddressSubSet(myself, RelationRelationId, new_rel_oid, i + 1);
-		ObjectAddressSet(referenced, TypeRelationId, attr->atttypid);
+		ObjectAddressSet(referenced, TypeRelationId,
+						 tupdesc->attrs[i].atttypid);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
-		if (OidIsValid(attr->attcollation) &&
-			attr->attcollation != DEFAULT_COLLATION_OID)
+		if (OidIsValid(tupdesc->attrs[i].attcollation) &&
+			tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
 		{
-			ObjectAddressSet(referenced, CollationRelationId, attr->attcollation);
+			ObjectAddressSet(referenced, CollationRelationId,
+							 tupdesc->attrs[i].attcollation);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 	}
@@ -838,17 +884,12 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{
-		for (i = 0; i < (int) lengthof(SysAtt); i++)
-		{
-			FormData_pg_attribute attStruct;
+		TupleDesc	td;
 
-			memcpy(&attStruct, SysAtt[i], sizeof(FormData_pg_attribute));
+		td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-			/* Fill in the correct relation OID in the copied tuple */
-			attStruct.attrelid = new_rel_oid;
-
-			InsertPgAttributeTuple(rel, &attStruct, (Datum) 0, indstate);
-		}
+		InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, indstate);
+		FreeTupleDesc(td);
 	}
 
 	/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8ec2864c76..1be27eec52 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -106,8 +106,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, int numatts,
-								  Datum *attopts);
+static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -485,12 +484,11 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
+AppendAttributeTuples(Relation indexRelation, Datum *attopts)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
 	TupleDesc	indexTupDesc;
-	int			i;
 
 	/*
 	 * open the attribute relation and its indexes
@@ -504,15 +502,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	for (i = 0; i < numatts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
-		Datum		attoptions = attopts ? attopts[i] : (Datum) 0;
-
-		Assert(attr->attnum == i + 1);
-
-		InsertPgAttributeTuple(pg_attribute, attr, attoptions, indstate);
-	}
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attopts, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -979,8 +969,7 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs,
-						  indexInfo->ii_OpclassOptions);
+	AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions);
 
 	/* ----------------
 	 *	  update pg_index
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index fe277f3ad3..538f6a06b8 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "executor/executor.h"
@@ -250,6 +251,41 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 	CatalogIndexInsert(indstate, tup);
 }
 
+/*
+ * CatalogTuplesMultiInsertWithInfo - as above, but for multiple tuples
+ *
+ * Insert multiple tuples into the given catalog relation at once, with an
+ * amortized cost of CatalogOpenIndexes.
+ */
+void
+CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
+								 int ntuples, CatalogIndexState indstate)
+{
+	/* Nothing to do */
+	if (ntuples <= 0)
+		return;
+
+	heap_multi_insert(heapRel, slot, ntuples,
+					  GetCurrentCommandId(true), 0, NULL);
+
+	/*
+	 * There is no equivalent to heap_multi_insert for the catalog indexes, so
+	 * we must loop over and insert individually.
+	 */
+	for (int i = 0; i < ntuples; i++)
+	{
+		bool		should_free;
+		HeapTuple	tuple;
+
+		tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free);
+		tuple->t_tableOid = slot[i]->tts_tableOid;
+		CatalogIndexInsert(indstate, tuple);
+
+		if (should_free)
+			heap_freetuple(tuple);
+	}
+}
+
 /*
  * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple
  *
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 082b935a69..ef2b87927c 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -785,6 +785,13 @@ checkSharedDependencies(Oid classId, Oid objectId,
 	return true;
 }
 
+
+/*
+ * Cap the maximum amount of bytes allocated for copyTemplateDependencies()
+ * slots.
+ */
+#define MAX_PGSHDEPEND_INSERT_BYTES 65535
+
 /*
  * copyTemplateDependencies
  *
@@ -799,14 +806,19 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
+	int			slotCount;
 	CatalogIndexState indstate;
-	Datum		values[Natts_pg_shdepend];
-	bool		nulls[Natts_pg_shdepend];
-	bool		replace[Natts_pg_shdepend];
+	TupleTableSlot **slot;
+	int			nslots;
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
 
+	nslots = MAX_PGSHDEPEND_INSERT_BYTES / sizeof(FormData_pg_shdepend);
+	slot = palloc(sizeof(TupleTableSlot *) * nslots);
+	for (int i = 0; i < nslots; i++)
+		slot[i] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+
 	indstate = CatalogOpenIndexes(sdepRel);
 
 	/* Scan all entries with dbid = templateDbId */
@@ -818,14 +830,6 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
 							  NULL, 1, key);
 
-	/* Set up to copy the tuples except for inserting newDbId */
-	memset(values, 0, sizeof(values));
-	memset(nulls, false, sizeof(nulls));
-	memset(replace, false, sizeof(replace));
-
-	replace[Anum_pg_shdepend_dbid - 1] = true;
-	values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(newDbId);
-
 	/*
 	 * Copy the entries of the original database, changing the database Id to
 	 * that of the new database.  Note that because we are not copying rows
@@ -833,20 +837,46 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	 * copy the ownership dependency of the template database itself; this is
 	 * what we want.
 	 */
+	slotCount = 0;
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		HeapTuple	newtup;
+		Form_pg_shdepend shdep;
 
-		newtup = heap_modify_tuple(tup, sdepDesc, values, nulls, replace);
-		CatalogTupleInsertWithInfo(sdepRel, newtup, indstate);
+		ExecClearTuple(slot[slotCount]);
 
-		heap_freetuple(newtup);
+		shdep = (Form_pg_shdepend) GETSTRUCT(tup);
+
+		slot[slotCount]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId);
+		slot[slotCount]->tts_values[Anum_pg_shdepend_classid] = shdep->classid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objid] = shdep->objid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid;
+		slot[slotCount]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype;
+
+		ExecStoreVirtualTuple(slot[slotCount]);
+		slotCount++;
+
+		/* If slots are full, insert a batch of tuples */
+		if (slotCount == nslots)
+		{
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+			slotCount = 0;
+		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (slotCount > 0)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+
 	systable_endscan(scan);
 
 	CatalogCloseIndexes(indstate);
 	table_close(sdepRel, RowExclusiveLock);
+
+	for (int i = 0; i < nslots; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 27b596cb59..ac53f79ada 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5975,6 +5975,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	AlterTableCmd *childcmd;
 	AclResult	aclresult;
 	ObjectAddress address;
+	TupleDesc	tupdesc;
+	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -6128,11 +6130,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
-	/* attribute.attacl is handled by InsertPgAttributeTuple */
+	/* attribute.attacl is handled by InsertPgAttributeTuples() */
 
 	ReleaseSysCache(typeTuple);
 
-	InsertPgAttributeTuple(attrdesc, &attribute, (Datum) 0, NULL);
+	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
#43Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#42)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 30 Jul 2020, at 03:28, Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Jul 29, 2020 at 11:34:07PM +0200, Daniel Gustafsson wrote:

Fair enough, let's break out pg_depend and I'll have another go at that.

Thanks. Attached is a polished version of the patch that I intend to
commit for pg_attribute and pg_shdepend. Let's look again at
pg_depend later, as there are also links with the handling of
dependencies for ALTER TABLE mainly.

Looks good, thanks. Let's close the CF entry with this and open a new for the
pg_depend part when that's done.

cheers ./daniel

#44Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#43)
1 attachment(s)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On Thu, Jul 30, 2020 at 11:23:38PM +0200, Daniel Gustafsson wrote:

Looks good, thanks. Let's close the CF entry with this and open a new for the
pg_depend part when that's done.

I have applied the patch, thanks.

And actually, I have found just after that CREATE DATABASE gets
severely impacted by the number of slots initialized when copying the
template dependencies if there are few of them. The fix is as simple
as delaying the initialization of the slots once we know they will be
used. In my initial tests, I was using fsync = off, so I missed that.
Sorry about that. The attached fixes this regression by delaying the
slot initialization until we know that it will be used. This does not
matter for pg_attribute as we know in advance the number of attributes
to insert.
--
Michael

Attachments:

pgshdepend-insert-fix.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index ef2b87927c..3c63f52a24 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -809,15 +809,18 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	int			slotCount;
 	CatalogIndexState indstate;
 	TupleTableSlot **slot;
-	int			nslots;
+	int			nslots, max_slots;
+	bool		slot_init = true;
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
 
-	nslots = MAX_PGSHDEPEND_INSERT_BYTES / sizeof(FormData_pg_shdepend);
-	slot = palloc(sizeof(TupleTableSlot *) * nslots);
-	for (int i = 0; i < nslots; i++)
-		slot[i] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+	/*
+	 * Allocate the slots to use, but delay initialization until we know
+	 * that they will be used.
+	 */
+	max_slots = MAX_PGSHDEPEND_INSERT_BYTES / sizeof(FormData_pg_shdepend);
+	slot = palloc(sizeof(TupleTableSlot *) * max_slots);
 
 	indstate = CatalogOpenIndexes(sdepRel);
 
@@ -842,6 +845,9 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	{
 		Form_pg_shdepend shdep;
 
+		if (slot_init)
+			slot[slotCount] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+
 		ExecClearTuple(slot[slotCount]);
 
 		shdep = (Form_pg_shdepend) GETSTRUCT(tup);
@@ -858,10 +864,11 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 		slotCount++;
 
 		/* If slots are full, insert a batch of tuples */
-		if (slotCount == nslots)
+		if (slotCount == max_slots)
 		{
 			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
 			slotCount = 0;
+			slot_init = false;
 		}
 	}
 
@@ -874,6 +881,8 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	CatalogCloseIndexes(indstate);
 	table_close(sdepRel, RowExclusiveLock);
 
+	/* drop only the number of slots used */
+	nslots = slot_init ? slotCount : max_slots;
 	for (int i = 0; i < nslots; i++)
 		ExecDropSingleTupleTableSlot(slot[i]);
 	pfree(slot);
#45Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#44)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

On 31 Jul 2020, at 04:42, Michael Paquier <michael@paquier.xyz> wrote:

On Thu, Jul 30, 2020 at 11:23:38PM +0200, Daniel Gustafsson wrote:

Looks good, thanks. Let's close the CF entry with this and open a new for the
pg_depend part when that's done.

I have applied the patch, thanks.

And actually, I have found just after that CREATE DATABASE gets
severely impacted by the number of slots initialized when copying the
template dependencies if there are few of them. The fix is as simple
as delaying the initialization of the slots once we know they will be
used. In my initial tests, I was using fsync = off, so I missed that.
Sorry about that. The attached fixes this regression by delaying the
slot initialization until we know that it will be used. This does not
matter for pg_attribute as we know in advance the number of attributes
to insert.

Right, that makes sense. Sorry for missing that, and thanks for fixing.

cheers ./daniel

#46Andres Freund
andres@anarazel.de
In reply to: Daniel Gustafsson (#43)
Re: Ought to use heap_multi_insert() for pg_attribute/depend insertions?

Hi,

On 2020-07-30 23:23:38 +0200, Daniel Gustafsson wrote:

On 30 Jul 2020, at 03:28, Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Jul 29, 2020 at 11:34:07PM +0200, Daniel Gustafsson wrote:

Fair enough, let's break out pg_depend and I'll have another go at that.

Thanks. Attached is a polished version of the patch that I intend to
commit for pg_attribute and pg_shdepend. Let's look again at
pg_depend later, as there are also links with the handling of
dependencies for ALTER TABLE mainly.

Looks good, thanks.

Nice work!

Greetings,

Andres Freund