From 71cfefec57388d7d90ae169a8122bdb97a7bcd14 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 3 Sep 2020 14:23:48 +0900
Subject: [PATCH v3 1/2] Use multi-inserts for pg_depend

This is a follow-up of the work done in e3931d01.  This case is a bit
different than pg_attribute and pg_shdepend: the maximum number of items
to insert is known in advance, but there is no need to handle pinned
dependencies.  Hence, the base allocation for slots is done based on the
number of items and the maximum allowed with a cap at 64kB, and items
are initialized once used to minimize the overhead of the operation.

Some of the multi-insert logic is also simplified for pg_shdepend, as
per the feedback discussed for this specific patch.

Author: Daniel Gustafsson, Michael Paquier
Discussion: https://postgr.es/m/XXX
---
 src/include/catalog/indexing.h    |  6 +++
 src/backend/catalog/heap.c        |  8 +--
 src/backend/catalog/pg_depend.c   | 88 +++++++++++++++++++++++--------
 src/backend/catalog/pg_shdepend.c | 66 +++++++++++------------
 4 files changed, 106 insertions(+), 62 deletions(-)

diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index a7e2a9b26b..d86729dc6c 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -29,6 +29,12 @@
  */
 typedef struct ResultRelInfo *CatalogIndexState;
 
+/*
+ * Cap the maximum amount of bytes allocated for multi-inserts with system
+ * catalogs, limiting the number of slots used.
+ */
+#define MAX_CATALOG_MULTI_INSERT_BYTES 65535
+
 /*
  * indexing.c prototypes
  */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index abd5bdb866..1201a93361 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -709,12 +709,6 @@ CheckAttributeType(const char *attname,
 	}
 }
 
-/*
- * 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.
@@ -750,7 +744,7 @@ InsertPgAttributeTuples(Relation pg_attribute_rel,
 
 	/* Initialize the number of slots to use */
 	nslots = Min(tupdesc->natts,
-				 (MAX_PGATTRIBUTE_INSERT_BYTES / sizeof(FormData_pg_attribute)));
+				 (MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_attribute)));
 	slot = palloc(sizeof(TupleTableSlot *) * nslots);
 	for (int i = 0; i < nslots; i++)
 		slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 70baf03178..406e36ec00 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -59,10 +59,11 @@ 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			i,
+				max_slots,
+				slot_init_count,
+				slot_stored_count;
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -76,11 +77,23 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 	dependDesc = table_open(DependRelationId, RowExclusiveLock);
 
+	/*
+	 * Allocate the slots to use, but delay initialization until we know that
+	 * they will be used.  The slot initialization is the costly part, and the
+	 * exact number of dependencies inserted cannot be known in advance as it
+	 * depends on what is pinned by the system.
+	 */
+	max_slots = Min(nreferenced,
+					MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_depend));
+	slot = palloc(sizeof(TupleTableSlot *) * max_slots);
+
 	/* Don't open indexes unless we need to make an update */
 	indstate = NULL;
 
-	memset(nulls, false, sizeof(nulls));
-
+	/* number of slots currently storing tuples */
+	slot_stored_count = 0;
+	/* number of slots currently initialized */
+	slot_init_count = 0;
 	for (i = 0; i < nreferenced; i++, referenced++)
 	{
 		/*
@@ -88,38 +101,69 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		 * 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;
+
+		if (slot_init_count < max_slots)
 		{
-			/*
-			 * 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[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
+															   &TTSOpsHeapTuple);
+			slot_init_count++;
+		}
 
-			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);
+		ExecClearTuple(slot[slot_stored_count]);
 
-			values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+		/*
+		 * Record the Dependency.  Note we don't bother to check for duplicate
+		 * dependencies; there's no harm in them.
+		 */
+		slot[slot_stored_count]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
+		slot[slot_stored_count]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
+		slot[slot_stored_count]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
+		slot[slot_stored_count]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior);
+		slot[slot_stored_count]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
+		slot[slot_stored_count]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
+		slot[slot_stored_count]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
 
-			tup = heap_form_tuple(dependDesc->rd_att, values, nulls);
+		memset(slot[slot_stored_count]->tts_isnull, false,
+			   slot[slot_stored_count]->tts_tupleDescriptor->natts * sizeof(bool));
 
+		ExecStoreVirtualTuple(slot[slot_stored_count]);
+		slot_stored_count++;
+
+		/* If slots are full, insert a batch of tuples */
+		if (slot_stored_count == max_slots)
+		{
 			/* fetch index info only when we know we need it */
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
 
-			CatalogTupleInsertWithInfo(dependDesc, tup, indstate);
-
-			heap_freetuple(tup);
+			CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count,
+											 indstate);
+			slot_stored_count = 0;
 		}
 	}
 
+	/* Insert any tuples left in the buffer */
+	if (slot_stored_count > 0)
+	{
+		/* fetch index info only when we know we need it */
+		if (indstate == NULL)
+			indstate = CatalogOpenIndexes(dependDesc);
+
+		CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count,
+										 indstate);
+	}
+
 	if (indstate != NULL)
 		CatalogCloseIndexes(indstate);
 
 	table_close(dependDesc, RowExclusiveLock);
+
+	/* Drop only the number of slots used */
+	for (i = 0; i < slot_init_count; i++)
+		ExecDropSingleTupleTableSlot(slot[i]);
+	pfree(slot);
 }
 
 /*
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 30b234e90e..81cd8689d5 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -786,12 +786,6 @@ checkSharedDependencies(Oid classId, Oid objectId,
 }
 
 
-/*
- * Cap the maximum amount of bytes allocated for copyTemplateDependencies()
- * slots.
- */
-#define MAX_PGSHDEPEND_INSERT_BYTES 65535
-
 /*
  * copyTemplateDependencies
  *
@@ -806,21 +800,22 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	ScanKeyData key[1];
 	SysScanDesc scan;
 	HeapTuple	tup;
-	int			slotCount;
 	CatalogIndexState indstate;
 	TupleTableSlot **slot;
-	int			nslots,
-				max_slots;
-	bool		slot_init = true;
+	int			max_slots,
+				slot_init_count,
+				slot_stored_count;
 
 	sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
 	sdepDesc = RelationGetDescr(sdepRel);
 
 	/*
 	 * Allocate the slots to use, but delay initialization until we know that
-	 * they will be used.
+	 * they will be used.  A full scan of pg_shdepend is done to find all the
+	 * dependencies from the template database to copy.  Their number is not
+	 * known in advance and the slot initialization is the costly part.
 	 */
-	max_slots = MAX_PGSHDEPEND_INSERT_BYTES / sizeof(FormData_pg_shdepend);
+	max_slots = MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_shdepend);
 	slot = palloc(sizeof(TupleTableSlot *) * max_slots);
 
 	indstate = CatalogOpenIndexes(sdepRel);
@@ -834,6 +829,11 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
 							  NULL, 1, key);
 
+	/* number of slots currently storing tuples */
+	slot_stored_count = 0;
+	/* number of slots currently initialized */
+	slot_init_count = 0;
+
 	/*
 	 * 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
@@ -841,41 +841,42 @@ 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)))
 	{
 		Form_pg_shdepend shdep;
 
-		if (slot_init)
-			slot[slotCount] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+		if (slot_init_count < max_slots)
+		{
+			slot[slot_stored_count] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple);
+			slot_init_count++;
+		}
 
-		ExecClearTuple(slot[slotCount]);
+		ExecClearTuple(slot[slot_stored_count]);
 
 		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;
+		slot[slot_stored_count]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId);
+		slot[slot_stored_count]->tts_values[Anum_pg_shdepend_classid] = shdep->classid;
+		slot[slot_stored_count]->tts_values[Anum_pg_shdepend_objid] = shdep->objid;
+		slot[slot_stored_count]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid;
+		slot[slot_stored_count]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid;
+		slot[slot_stored_count]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid;
+		slot[slot_stored_count]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype;
 
-		ExecStoreVirtualTuple(slot[slotCount]);
-		slotCount++;
+		ExecStoreVirtualTuple(slot[slot_stored_count]);
+		slot_stored_count++;
 
 		/* If slots are full, insert a batch of tuples */
-		if (slotCount == max_slots)
+		if (slot_stored_count == max_slots)
 		{
-			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
-			slotCount = 0;
-			slot_init = false;
+			CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate);
+			slot_stored_count = 0;
 		}
 	}
 
 	/* Insert any tuples left in the buffer */
-	if (slotCount > 0)
-		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate);
+	if (slot_stored_count > 0)
+		CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate);
 
 	systable_endscan(scan);
 
@@ -883,8 +884,7 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 	table_close(sdepRel, RowExclusiveLock);
 
 	/* Drop only the number of slots used */
-	nslots = slot_init ? slotCount : max_slots;
-	for (int i = 0; i < nslots; i++)
+	for (int i = 0; i < slot_init_count; i++)
 		ExecDropSingleTupleTableSlot(slot[i]);
 	pfree(slot);
 }
-- 
2.28.0

