From 89bb807c11ec411d1e25b0aa03792ae341435fec Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Tue, 13 Mar 2018 17:29:32 +0900
Subject: [PATCH 4/4] PoC of generic plan removal of plancachesource.

---
 src/backend/utils/cache/plancache.c | 157 ++++++++++++++++++++++++++++++++++++
 src/backend/utils/hash/dynahash.c   |  16 +++-
 src/backend/utils/misc/guc.c        |  21 +++++
 src/backend/utils/mmgr/mcxt.c       |   1 +
 src/include/commands/prepare.h      |   4 +
 src/include/utils/hsearch.h         |   2 +
 src/include/utils/plancache.h       |  14 +++-
 7 files changed, 208 insertions(+), 7 deletions(-)

diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8d7d8e04c9..9e34e4098e 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,13 @@
  * 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;
+int						 plancache_prune_min_age = 600;
 
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
@@ -105,6 +114,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);
 
 
 /*
@@ -207,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);
 
@@ -422,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
  *
@@ -469,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;
@@ -491,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;
@@ -501,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;
 	}
 
@@ -536,6 +588,11 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
 		Assert(plan->magic == CACHEDPLAN_MAGIC);
 		plansource->gplan = NULL;
 		ReleaseCachedPlan(plan, false);
+		if (plansource->is_saved)
+		{
+			Assert (num_saved_plans >= 1);
+			num_saved_plans--;
+		}
 	}
 }
 
@@ -1146,6 +1203,15 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 	if (useResOwner && !plansource->is_saved)
 		elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
 
+	/* increment access counter and set timestamp */
+	if (plansource->is_saved)
+	{
+		plansource->last_access = GetCatCacheClock();
+
+		/* move this plan to the first of the list if needed */
+		MovePlansourceToFirst(plansource);
+	}
+
 	/* Make sure the querytree list is valid and we have parse-time locks */
 	qlist = RevalidateCachedQuery(plansource, queryEnv);
 
@@ -1154,6 +1220,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 */
@@ -1166,6 +1237,12 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 			plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
 			/* Just make real sure plansource->gplan is clear */
 			ReleaseGenericPlan(plansource);
+
+
+			/* Prune cached plans if needed */
+			if (plansource->is_saved)
+				num_saved_plans++;
+
 			/* Link the new generic plan into the plansource */
 			plansource->gplan = plan;
 			plan->refcount++;
@@ -1853,6 +1930,86 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue)
 	ResetPlanCache();
 }
 
+/*
+ * PrunePlanCache: invalidate "old" cached plans.
+ */
+static void
+PruneCachedPlan(void)
+{
+	CachedPlanSource *plansource;
+	TimestampTz		  currclock = GetCatCacheClock();
+	long			  age;
+	int				  us;
+	int				  nremoved = 0;
+
+	/* do nothing if not wanted */
+	if (plancache_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 < plancache_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);
+
+		/*
+		 * 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 <= plancache_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 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/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index 5a8b15652a..a5b4979662 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -187,6 +187,8 @@ struct HASHHDR
 	int			nelem_alloc;	/* number of entries to allocate at once */
 	bool		prunable;		/* true if prunable */
 	HASH_PRUNE_CB	prune_cb;	/* pruning callback. see above. */
+	int		   *memory_target;	/* pointer to memory target */
+	int		   *prune_min_age;	/* pointer to prune minimum age */
 
 #ifdef HASH_STATISTICS
 
@@ -510,6 +512,14 @@ hash_create(const char *tabname, long nelem, HASHCTL *info, int flags)
 	{
 		hctl->prunable = true;
 		hctl->prune_cb = info->prune_cb;
+		if (info->memory_target)
+			hctl->memory_target = info->memory_target;
+		else
+			hctl->memory_target = &syscache_memory_target;
+		if (info->prune_min_age)
+			hctl->prune_min_age = info->prune_min_age;
+		else
+			hctl->prune_min_age = &syscache_prune_min_age;
 	}
 	else
 		hctl->prunable = false;
@@ -1654,7 +1664,7 @@ prune_entries(HTAB *hashp)
 		has_seq_scans(hashp));
 
 	/* This setting prevents pruning */
-	if (syscache_prune_min_age < 0)
+	if (*hctl->prune_min_age < 0)
 		return false;
 
 	/*
@@ -1663,7 +1673,7 @@ prune_entries(HTAB *hashp)
 	 * settings is shared with syscache
 	 */
 	if (hctl->dsize * sizeof(HASHBUCKET) * hashp->ssize <
-		(Size) syscache_memory_target * 1024L)
+		(Size) *hctl->memory_target * 1024L)
 		return false;
 
 	/*
@@ -1683,7 +1693,7 @@ prune_entries(HTAB *hashp)
 		TimestampDifference(helm->last_access, currclock, &entry_age, &us);
 
 		/* settings is shared with syscache */
-		if (entry_age > syscache_prune_min_age)
+		if (entry_age > *hctl->prune_min_age)
 		{
 			/* Wait for the next chance if this is recently used */
 			if (helm->naccess > 0)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5e0d18657f..45aab61d62 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1995,6 +1995,27 @@ 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
+	},
+
+	{
+		{"plancache_prune_min_age", PGC_USERSET, RESOURCES_MEM,
+			gettext_noop("Sets the minimum duration of plancache entries to remove."),
+			gettext_noop("Plancache items that live unused for loger than this seconds are considered to be removed."),
+		 	GUC_UNIT_S
+		},
+		&plancache_prune_min_age,
+		600, -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/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index d7baa54808..db225a06da 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -194,6 +194,7 @@ MemoryContextResetChildren(MemoryContext context)
  * but we have to recurse to handle the children.
  * We must also delink the context from its parent, if it has one.
  */
+int hoge = 0;
 void
 MemoryContextDelete(MemoryContext context)
 {
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index ffec029df4..1a8e8dd50e 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -31,6 +31,10 @@ typedef struct
 	CachedPlanSource *plansource;	/* the actual cached plan */
 	bool		from_sql;		/* prepared via SQL, not FE/BE protocol? */
 	TimestampTz prepare_time;	/* the time when the stmt was prepared */
+	RawStmt	   *raw_stmt;
+	int			num_params;
+	Oid		   *param_types;
+	List	   *query_list;
 } PreparedStatement;
 
 
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index df12352a46..7ea3c75423 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -93,6 +93,8 @@ typedef struct HASHCTL
 	MemoryContext hcxt;			/* memory context to use for allocations */
 	HASHHDR    *hctl;			/* location of header in shared mem */
 	HASH_PRUNE_CB	prune_cb;	/* pruning callback. see above. */
+	int		   *memory_target;	/* pointer to memory target */
+	int		   *prune_min_age;	/* pointer to prune minimum age */
 } HASHCTL;
 
 /* Flags to indicate which parameters are supplied */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index ab20aa04b0..b5d439985c 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -72,10 +72,11 @@ struct RawStmt;
  * is no way to free memory short of clearing that entire context.  A oneshot
  * plan is always treated as unsaved.
  *
- * Note: the string referenced by commandTag is not subsidiary storage;
- * it is assumed to be a compile-time-constant string.  As with portals,
- * commandTag shall be NULL if and only if the original query string (before
- * rewriting) was an empty string.
+ * Note: the string referenced by commandTag is not subsidiary storage; it is
+ * assumed to be a compile-time-constant string.  As with portals, commandTag
+ * shall be NULL if and only if the original query string (before rewriting)
+ * was an empty string. For memory-saving purpose, this struct is separated
+ * into to parts, the latter is removable in inactive state.
  */
 typedef struct CachedPlanSource
 {
@@ -110,11 +111,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 *prev_saved;	/* list link, if so */
 	struct CachedPlanSource *next_saved;	/* list 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 +146,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.2

