From f5ef702ffaeeaa3ca11ecd042abc9b63b5b6db83 Mon Sep 17 00:00:00 2001
From: Kirk Jamison <k.jamison@jp.fujitsu.com>
Date: Wed, 28 Oct 2020 06:32:01 +0000
Subject: [PATCH v29 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 | 63 +++++++++++++++++++++++++++++++++++--
 src/backend/storage/smgr/smgr.c     | 14 ++++-----
 src/include/storage/bufmgr.h        |  2 +-
 3 files changed, 68 insertions(+), 11 deletions(-)

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 623fe6e..e3ed565 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -3121,17 +3121,32 @@ DropRelFileNodeBuffers(SMgrRelation smgr_reln, ForkNumber *forkNum,
  * --------------------------------------------------------------------
  */
 void
-DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
+DropRelFileNodesAllBuffers(SMgrRelation *smgr_reln, int nnodes)
 {
 	int			i,
+				j,
+				nforks,
 				n = 0;
+	SMgrRelation	*rels;
+	RelFileNodeBackend *rnodes;
 	RelFileNode *nodes;
+	BlockNumber	nblocks;
+	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++)
@@ -3142,7 +3157,10 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 				DropRelFileNodeAllLocalBuffers(rnodes[i].node);
 		}
 		else
+		{
+			rels[n] = smgr_reln[i];
 			nodes[n++] = rnodes[i].node;
+		}
 	}
 
 	/*
@@ -3152,10 +3170,48 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 	if (n == 0)
 	{
 		pfree(nodes);
+		pfree(rels);
+		pfree(rnodes);
 		return;
 	}
 
 	/*
+	 * Zero the array of blocks because these will all be dropped anyway.
+	 * We don't care if the block count of relation fork cannot be trusted,
+	 * 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 = smgrnblocks(rels[i], j, NULL);
+
+			nBlocksToInvalidate += nblocks;
+
+			forks[nforks++] = j;
+		}
+		if (nBlocksToInvalidate >= BUF_DROP_FULL_SCAN_THRESHOLD)
+			goto buffer_full_scan;
+
+		DropRelFileNodeBuffers(rels[i], forks, nforks, firstDelBlocks);
+	}
+	pfree(nodes);
+	pfree(rels);
+	pfree(rnodes);
+	return;
+
+buffer_full_scan:
+	/*
 	 * For low number of relations to drop just use a simple walk through, to
 	 * save the bsearch overhead. The threshold to use is rather a guess than
 	 * an exactly determined value, as it depends on many factors (CPU and RAM
@@ -3208,8 +3264,9 @@ DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes)
 		else
 			UnlockBufHdr(bufHdr, buf_state);
 	}
-
 	pfree(nodes);
+	pfree(rels);
+	pfree(rnodes);
 }
 
 /* ---------------------------------------------------------------------
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 6b8528e..fd2cf84 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 37093fb..0cc40e4 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

