From 842f7b9fd47c6ee4daf1316547679d4298538940 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 15 Mar 2018 12:04:43 +0900
Subject: [PATCH 4/4] Generic plan removal of PlanCacheSource.

We cannot remove saved cached plans while pruning since they are
pointed from other structures. But still we can remove generic plan of
each saved plans. The behavior is controled by two additional GUC
variables min_cached_plans and cache_prune_min_age. The former tells
to keep that number of generic plans without pruned. The latter tells
how long we shuold keep generic plans before pruning.
---
 src/backend/utils/cache/plancache.c | 163 ++++++++++++++++++++++++++++++++++++
 src/backend/utils/misc/guc.c        |  10 +++
 src/include/utils/plancache.h       |   7 +-
 3 files changed, 179 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 0ad3e3c736..701ead152c 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -63,12 +63,14 @@
 #include "storage/lmgr.h"
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
+#include "utils/catcache.h"
 #include "utils/inval.h"
 #include "utils/memutils.h"
 #include "utils/resowner_private.h"
 #include "utils/rls.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/timestamp.h"
 
 
 /*
@@ -86,6 +88,12 @@
  * guarantee to save a CachedPlanSource without error.
  */
 static CachedPlanSource *first_saved_plan = NULL;
+static CachedPlanSource *last_saved_plan = NULL;
+static int				 num_saved_plans = 0;
+static TimestampTz		 oldest_saved_plan = 0;
+
+/* GUC variables */
+int						 min_cached_plans = 1000;
 
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
@@ -105,6 +113,7 @@ static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
 static void PlanCacheRelCallback(Datum arg, Oid relid);
 static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
+static void PruneCachedPlan(void);
 
 
 /*
@@ -208,6 +217,8 @@ CreateCachedPlan(RawStmt *raw_parse_tree,
 	plansource->generic_cost = -1;
 	plansource->total_custom_cost = 0;
 	plansource->num_custom_plans = 0;
+	plansource->last_access = GetCatCacheClock();
+	
 
 	MemoryContextSwitchTo(oldcxt);
 
@@ -423,6 +434,28 @@ CompleteCachedPlan(CachedPlanSource *plansource,
 	plansource->is_valid = true;
 }
 
+/* moves the plansource to the first in the list */
+static inline void
+MovePlansourceToFirst(CachedPlanSource *plansource)
+{
+	if (first_saved_plan != plansource)
+	{
+		/* delink this element */
+		if (plansource->next_saved)
+			plansource->next_saved->prev_saved = plansource->prev_saved;
+		if (plansource->prev_saved)
+			plansource->prev_saved->next_saved = plansource->next_saved;
+		if (last_saved_plan == plansource)
+			last_saved_plan = plansource->prev_saved;
+
+		/* insert at the beginning */
+		first_saved_plan->prev_saved = plansource;
+		plansource->next_saved = first_saved_plan;
+		plansource->prev_saved = NULL;
+		first_saved_plan = plansource;
+	}
+}
+
 /*
  * SaveCachedPlan: save a cached plan permanently
  *
@@ -470,6 +503,11 @@ SaveCachedPlan(CachedPlanSource *plansource)
 	 * Add the entry to the global list of cached plans.
 	 */
 	plansource->next_saved = first_saved_plan;
+	if (first_saved_plan)
+		first_saved_plan->prev_saved = plansource;
+	else
+		last_saved_plan = plansource;
+	plansource->prev_saved = NULL;
 	first_saved_plan = plansource;
 
 	plansource->is_saved = true;
@@ -492,7 +530,11 @@ DropCachedPlan(CachedPlanSource *plansource)
 	if (plansource->is_saved)
 	{
 		if (first_saved_plan == plansource)
+		{
 			first_saved_plan = plansource->next_saved;
+			if (first_saved_plan)
+				first_saved_plan->prev_saved = NULL;
+		}
 		else
 		{
 			CachedPlanSource *psrc;
@@ -502,10 +544,19 @@ DropCachedPlan(CachedPlanSource *plansource)
 				if (psrc->next_saved == plansource)
 				{
 					psrc->next_saved = plansource->next_saved;
+					if (psrc->next_saved)
+						psrc->next_saved->prev_saved = psrc;
 					break;
 				}
 			}
 		}
+
+		if (last_saved_plan == plansource)
+		{
+			last_saved_plan = plansource->prev_saved;
+			if (last_saved_plan)
+				last_saved_plan->next_saved = NULL;
+		}
 		plansource->is_saved = false;
 	}
 
@@ -537,6 +588,13 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
 		Assert(plan->magic == CACHEDPLAN_MAGIC);
 		plansource->gplan = NULL;
 		ReleaseCachedPlan(plan, false);
+
+		/* decrement "saved plans" counter */
+		if (plansource->is_saved)
+		{
+			Assert (num_saved_plans > 0);
+			num_saved_plans--;
+		}
 	}
 }
 
@@ -1148,6 +1206,17 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 	if (useResOwner && !plansource->is_saved)
 		elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
 
+	/*
+	 * set last-accessed timestamp and move this plan to the first of the list
+	 */
+	if (plansource->is_saved)
+	{
+		plansource->last_access = GetCatCacheClock();
+
+		/* move this plan to the first of the list */
+		MovePlansourceToFirst(plansource);
+	}
+
 	/* Make sure the querytree list is valid and we have parse-time locks */
 	qlist = RevalidateCachedQuery(plansource, queryEnv);
 
@@ -1156,6 +1225,11 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	if (!customplan)
 	{
+		/* Prune cached plans if needed */
+		if (plansource->is_saved &&
+			min_cached_plans >= 0 && num_saved_plans > min_cached_plans)
+				PruneCachedPlan();
+
 		if (CheckCachedPlan(plansource))
 		{
 			/* We want a generic plan, and we already have a valid one */
@@ -1168,6 +1242,11 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 			plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
 			/* Just make real sure plansource->gplan is clear */
 			ReleaseGenericPlan(plansource);
+
+			/* count this new saved plan */
+			if (plansource->is_saved)
+				num_saved_plans++;
+
 			/* Link the new generic plan into the plansource */
 			plansource->gplan = plan;
 			plan->refcount++;
@@ -1856,6 +1935,90 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue)
 	ResetPlanCache();
 }
 
+/*
+ * PrunePlanCache: removes generic plan of "old" saved plans.
+ */
+static void
+PruneCachedPlan(void)
+{
+	CachedPlanSource *plansource;
+	TimestampTz		  currclock = GetCatCacheClock();
+	long			  age;
+	int				  us;
+	int				  nremoved = 0;
+
+	/* do nothing if not wanted */
+	if (cache_prune_min_age < 0 || num_saved_plans <= min_cached_plans)
+		return;
+
+	/* Fast check for oldest cache */
+	if (oldest_saved_plan > 0)
+	{
+		TimestampDifference(oldest_saved_plan, currclock, &age, &us);
+		if (age < cache_prune_min_age)
+			return;
+	}		
+
+	/* last plan is the oldest. */
+	for (plansource = last_saved_plan; plansource; plansource = plansource->prev_saved)
+	{
+		long	plan_age;
+		int		us;
+
+		Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+
+		/* we want to prune no more plans */
+		if (num_saved_plans <= min_cached_plans)
+			break;
+
+		/*
+		 * No work if it already doesn't have gplan and move it to the
+		 * beginning so that we don't see it at the next time
+		 */
+		if (!plansource->gplan)
+			continue;
+
+		/*
+		 * Check age for pruning. Can exit immediately when finding a
+		 * not-older element.
+		 */
+		TimestampDifference(plansource->last_access, currclock, &plan_age, &us);
+		if (plan_age <= cache_prune_min_age)
+		{
+			/* this entry is the next oldest */
+			oldest_saved_plan = plansource->last_access;
+			break;
+		}
+
+		/*
+		 * Here, remove generic plans of this plansrouceif it is not actually
+		 * used and move it to the beginning of the list. Just update
+		 * last_access and move it to the beginning if the plan is used.
+		 */
+		if (plansource->gplan->refcount <= 1)
+		{
+			ReleaseGenericPlan(plansource);
+			nremoved++;
+		}
+
+		plansource->last_access = currclock;
+	}
+
+	/* move the "removed" plansrouces altogehter to the beginning of the list */
+	if (plansource != last_saved_plan && plansource)
+	{
+		plansource->next_saved->prev_saved = NULL;
+		first_saved_plan->prev_saved = last_saved_plan;
+ 		last_saved_plan->next_saved = first_saved_plan;
+		first_saved_plan = plansource->next_saved;
+		plansource->next_saved = NULL;
+		last_saved_plan = plansource;
+	}
+
+	if (nremoved > 0)
+		elog(DEBUG1, "plancache removed %d/%d", nremoved, num_saved_plans);
+}
+
 /*
  * ResetPlanCache: invalidate all cached plans.
  */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9800252965..478bfe96a4 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2128,6 +2128,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"min_cached_plans", PGC_USERSET, RESOURCES_MEM,
+			gettext_noop("Sets the minimum number of cached plans kept on memory."),
+			gettext_noop("Timeout invalidation of plancache is not activated until the number of plancaches reaches this value. -1 means timeout invalidation is always active.")
+		},
+		&min_cached_plans,
+		1000, -1, INT_MAX,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * We use the hopefully-safely-small value of 100kB as the compiled-in
 	 * default for max_stack_depth.  InitializeGUCOptions will increase it if
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index ab20aa04b0..f3c5b2010d 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -110,11 +110,13 @@ typedef struct CachedPlanSource
 	bool		is_valid;		/* is the query_list currently valid? */
 	int			generation;		/* increments each time we create a plan */
 	/* If CachedPlanSource has been saved, it is a member of a global list */
-	struct CachedPlanSource *next_saved;	/* list link, if so */
+	struct CachedPlanSource *prev_saved;	/* list prev link, if so */
+	struct CachedPlanSource *next_saved;	/* list next link, if so */
 	/* State kept to help decide whether to use custom or generic plans: */
 	double		generic_cost;	/* cost of generic plan, or -1 if not known */
 	double		total_custom_cost;	/* total cost of custom plans so far */
 	int			num_custom_plans;	/* number of plans included in total */
+	TimestampTz	last_access;	/* timestamp of the last usage */
 } CachedPlanSource;
 
 /*
@@ -143,6 +145,9 @@ typedef struct CachedPlan
 	MemoryContext context;		/* context containing this CachedPlan */
 } CachedPlan;
 
+/* GUC variables */
+extern int min_cached_plans;
+extern int plancache_prune_min_age;
 
 extern void InitPlanCache(void);
 extern void ResetPlanCache(void);
-- 
2.16.3

