From 882c3b8bf2641d7c02aafa8f94295fe9ec2c3a38 Mon Sep 17 00:00:00 2001
From: Kirk Jamison <k.jamison@jp.fujitsu.com>
Date: Tue, 10 Nov 2020 08:24:04 +0000
Subject: [PATCH v31 4/4] TRUNCATE optimization.

DropRelFileNodesAllBuffers() is optimized to skip the time-consuming
scan of the whole buffer pool when the relation is small enough, or
when the number of blocks to be invalidated is below the full scan
threshold. This improves the performance when the TRUNCATE command
truncated off any of the empty pages at the end of relation.
---
 src/backend/storage/buffer/bufmgr.c | 91 +++++++++++++++++++++++++++++++++----
 src/backend/storage/smgr/smgr.c     | 14 +++---
 src/include/storage/bufmgr.h        |  2 +-
 3 files changed, 90 insertions(+), 17 deletions(-)

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 67284e3..f8be12c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -3086,17 +3086,34 @@ DropRelFileNodeBuffers(SMgrRelation smgr_reln, ForkNumber *forkNum,
  * --------------------------------------------------------------------
  */
 void
-DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
+DropRelFileNodesAllBuffers(SMgrRelation *smgr_reln, int nnodes)
 {
 	int			i,
-				n = 0;
+				j,
+				nforks,
+				n = 0,
+				nrels = 0;
+	SMgrRelation	*rels;
+	RelFileNodeBackend *rnodes;
 	RelFileNode *nodes;
+	RelFileNode *undropped_nodes;
+	BlockNumber	nblocks[MAX_FORKNUM + 1];
+	BlockNumber	nBlocksToInvalidate;
+	BlockNumber	firstDelBlocks[MAX_FORKNUM + 1];
+	ForkNumber	forks[MAX_FORKNUM + 1];
 	bool		use_bsearch;
 
 	if (nnodes == 0)
 		return;
 
-	nodes = palloc(sizeof(RelFileNode) * nnodes);	/* non-local relations */
+	/* Create an array which contains all relations to be dropped */
+	rnodes = palloc(sizeof(RelFileNodeBackend) * nnodes);
+	for (i = 0; i < nnodes; i++)
+		rnodes[i] = smgr_reln[i]->smgr_rnode;
+
+	/* non-local relations */
+	rels = (SMgrRelation *)palloc(nnodes * sizeof(SMgrRelation));
+	nodes = palloc(sizeof(RelFileNode) * nnodes);
 
 	/* If it's a local relation, it's localbuf.c's problem. */
 	for (i = 0; i < nnodes; i++)
@@ -3107,7 +3124,10 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 				DropRelFileNodeAllLocalBuffers(rnodes[i].node);
 		}
 		else
+		{
+			rels[n] = smgr_reln[i];
 			nodes[n++] = rnodes[i].node;
+		}
 	}
 
 	/*
@@ -3117,6 +3137,57 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 	if (n == 0)
 	{
 		pfree(nodes);
+		pfree(rels);
+		pfree(rnodes);
+		return;
+	}
+
+	/* undropped non-local relations */
+	undropped_nodes = palloc(sizeof(RelFileNode) * n);
+
+	/*
+	 * Zero the array of blocks because these will all be dropped anyway.
+	 * We don't care yet whether or not the block count of relation fork
+	 * is cached as it will be checked in DropRelFileNodeBuffers.  However,
+	 * we give up the optimization if the relation is large enough that the
+	 * number of blocks to be invalidated exceeds the threshold for full scan.
+	 */
+	memset(firstDelBlocks, 0, sizeof(firstDelBlocks));
+	for (i = 0; i < n; i++)
+	{
+		nforks = 0;
+		nBlocksToInvalidate = 0;
+
+		for (j = 0; j <= MAX_FORKNUM; j++)
+		{
+			if (!smgrexists(rels[i], j))
+				continue;
+
+			/* Get the number of blocks for a relation's fork */
+			nblocks[nforks] = smgrnblocks(rels[i], j, NULL);
+
+			nBlocksToInvalidate += nblocks[nforks];
+
+			forks[nforks++] = j;
+		}
+
+		if (nBlocksToInvalidate < BUF_DROP_FULL_SCAN_THRESHOLD)
+			FindAndDropRelFileNodeBuffers(nodes[i], forks, nforks, nblocks,
+										  firstDelBlocks);
+		else
+			undropped_nodes[nrels++] = nodes[i];
+	}
+
+	/*
+	 * If there are no undropped nodes, then we're done. Release the
+	 * memory and return.
+	 */
+	if (nrels == 0)
+	{
+		pfree(undropped_nodes);
+		pfree(nodes);
+		pfree(rels);
+		pfree(rnodes);
 		return;
 	}
 
@@ -3126,11 +3197,11 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 	 * an exactly determined value, as it depends on many factors (CPU and RAM
 	 * speeds, amount of shared buffers etc.).
 	 */
-	use_bsearch = n > RELS_BSEARCH_THRESHOLD;
+	use_bsearch = nrels > RELS_BSEARCH_THRESHOLD;
 
 	/* sort the list of rnodes if necessary */
 	if (use_bsearch)
-		pg_qsort(nodes, n, sizeof(RelFileNode), rnode_comparator);
+		pg_qsort(undropped_nodes, nrels, sizeof(RelFileNode), rnode_comparator);
 
 	for (i = 0; i < NBuffers; i++)
 	{
@@ -3149,9 +3220,9 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 
 			for (j = 0; j < n; j++)
 			{
-				if (RelFileNodeEquals(bufHdr->tag.rnode, nodes[j]))
+				if (RelFileNodeEquals(bufHdr->tag.rnode, undropped_nodes[j]))
 				{
-					rnode = &nodes[j];
+					rnode = &undropped_nodes[j];
 					break;
 				}
 			}
@@ -3159,7 +3230,7 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 		else
 		{
 			rnode = bsearch((const void *) &(bufHdr->tag.rnode),
-							nodes, n, sizeof(RelFileNode),
+							undropped_nodes, nrels, sizeof(RelFileNode),
 							rnode_comparator);
 		}
 
@@ -3173,8 +3244,10 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 		else
 			UnlockBufHdr(bufHdr, buf_state);
 	}
-
+	pfree(undropped_nodes);
 	pfree(nodes);
+	pfree(rels);
+	pfree(rnodes);
 }
 
 /* ---------------------------------------------------------------------
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 9d3a67c..3663bb7 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -392,7 +392,13 @@ smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo)
 		return;
 
 	/*
-	 * create an array which contains all relations to be dropped, and close
+	 * Get rid of any remaining buffers for the relations.  bufmgr will just
+	 * drop them without bothering to write the contents.
+	 */
+	DropRelFileNodesAllBuffers(rels, nrels);
+
+	/*
+	 * Create an array which contains all relations to be dropped, and close
 	 * each relation's forks at the smgr level while at it
 	 */
 	rnodes = palloc(sizeof(RelFileNodeBackend) * nrels);
@@ -409,12 +415,6 @@ smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo)
 	}
 
 	/*
-	 * Get rid of any remaining buffers for the relations.  bufmgr will just
-	 * drop them without bothering to write the contents.
-	 */
-	DropRelFileNodesAllBuffers(rnodes, nrels);
-
-	/*
 	 * It'd be nice to tell the stats collector to forget them immediately,
 	 * too. But we can't because we don't know the OIDs.
 	 */
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 056f65e..2e5189b 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -205,7 +205,7 @@ extern void FlushRelationsAllBuffers(struct SMgrRelationData **smgrs, int nrels)
 extern void FlushDatabaseBuffers(Oid dbid);
 extern void DropRelFileNodeBuffers(struct SMgrRelationData *smgr_reln, ForkNumber *forkNum,
 								   int nforks, BlockNumber *firstDelBlock);
-extern void DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes);
+extern void DropRelFileNodesAllBuffers(struct SMgrRelationData **smgr_reln, int nnodes);
 extern void DropDatabaseBuffers(Oid dbid);
 
 #define RelationGetNumberOfBlocks(reln) \
-- 
1.8.3.1

