diff options
author | Tom Lane | 2011-09-16 04:42:53 +0000 |
---|---|---|
committer | Tom Lane | 2011-09-16 04:43:52 +0000 |
commit | e6faf910d75027bdce7cd0f2033db4e912592bcc (patch) | |
tree | b5fdc2340cc1cdf27dd473e23a09cb2953b5053c /src/include | |
parent | 09e98a3e170ecdeb25a0e1afe81bdbeeeaf21f48 (diff) |
Redesign the plancache mechanism for more flexibility and efficiency.
Rewrite plancache.c so that a "cached plan" (which is rather a misnomer
at this point) can support generation of custom, parameter-value-dependent
plans, and can make an intelligent choice between using custom plans and
the traditional generic-plan approach. The specific choice algorithm
implemented here can probably be improved in future, but this commit is
all about getting the mechanism in place, not the policy.
In addition, restructure the API to greatly reduce the amount of extraneous
data copying needed. The main compromise needed to make that possible was
to split the initial creation of a CachedPlanSource into two steps. It's
worth noting in particular that SPI_saveplan is now deprecated in favor of
SPI_keepplan, which accomplishes the same end result with zero data
copying, and no need to then spend even more cycles throwing away the
original SPIPlan. The risk of long-term memory leaks while manipulating
SPIPlans has also been greatly reduced. Most of this improvement is based
on use of the recently-added MemoryContextSetParent primitive.
Diffstat (limited to 'src/include')
-rw-r--r-- | src/include/catalog/namespace.h | 1 | ||||
-rw-r--r-- | src/include/commands/prepare.h | 8 | ||||
-rw-r--r-- | src/include/executor/spi.h | 1 | ||||
-rw-r--r-- | src/include/executor/spi_priv.h | 41 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 3 | ||||
-rw-r--r-- | src/include/utils/memutils.h | 1 | ||||
-rw-r--r-- | src/include/utils/plancache.h | 156 |
7 files changed, 126 insertions, 85 deletions
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 4bcbc20497f..904c6fd97d8 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -118,6 +118,7 @@ extern Oid GetTempToastNamespace(void); extern void ResetTempTableNamespace(void); extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context); +extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path); extern void PushOverrideSearchPath(OverrideSearchPath *newpath); extern void PopOverrideSearchPath(void); diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index 63c06ad8d69..52362fa933d 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -44,13 +44,7 @@ extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, /* Low-level access to stored prepared statements */ extern void StorePreparedStatement(const char *stmt_name, - Node *raw_parse_tree, - const char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, + CachedPlanSource *plansource, bool from_sql); extern PreparedStatement *FetchPreparedStatement(const char *stmt_name, bool throwError); diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 7199debb27a..3b1b27ee49e 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -93,6 +93,7 @@ extern SPIPlanPtr SPI_prepare_params(const char *src, ParserSetupHook parserSetup, void *parserSetupArg, int cursorOptions); +extern int SPI_keepplan(SPIPlanPtr plan); extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan); extern int SPI_freeplan(SPIPlanPtr plan); diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h index 5865f532802..3e7bf860948 100644 --- a/src/include/executor/spi_priv.h +++ b/src/include/executor/spi_priv.h @@ -32,27 +32,32 @@ typedef struct } _SPI_connection; /* - * SPI plans have two states: saved or unsaved. + * SPI plans have three states: saved, unsaved, or temporary. * - * For an unsaved plan, the _SPI_plan struct and all its subsidiary data are in - * a dedicated memory context identified by plancxt. An unsaved plan is good - * at most for the current transaction, since the locks that protect it from - * schema changes will be lost at end of transaction. Hence the plancxt is - * always a transient one. + * Ordinarily, the _SPI_plan struct itself as well as the argtypes array + * are in a dedicated memory context identified by plancxt (which can be + * really small). All the other subsidiary state is in plancache entries + * identified by plancache_list (note: the list cells themselves are in + * plancxt). * - * For a saved plan, the _SPI_plan struct and the argument type array are in - * the plancxt (which can be really small). All the other subsidiary state - * is in plancache entries identified by plancache_list (note: the list cells - * themselves are in plancxt). We rely on plancache.c to keep the cache - * entries up-to-date as needed. The plancxt is a child of CacheMemoryContext - * since it should persist until explicitly destroyed. + * In an unsaved plan, the plancxt as well as the plancache entries' contexts + * are children of the SPI procedure context, so they'll all disappear at + * function exit. plancache.c also knows that the plancache entries are + * "unsaved", so it doesn't link them into its global list; hence they do + * not respond to inval events. This is OK since we are presumably holding + * adequate locks to prevent other backends from messing with the tables. * - * To avoid redundant coding, the representation of unsaved plans matches - * that of saved plans, ie, plancache_list is a list of CachedPlanSource - * structs which in turn point to CachedPlan structs. However, in an unsaved - * plan all these structs are just created by spi.c and are not known to - * plancache.c. We don't try very hard to make all their fields valid, - * only the ones spi.c actually uses. + * For a saved plan, the plancxt is made a child of CacheMemoryContext + * since it should persist until explicitly destroyed. Likewise, the + * plancache entries will be under CacheMemoryContext since we tell + * plancache.c to save them. We rely on plancache.c to keep the cache + * entries up-to-date as needed in the face of invalidation events. + * + * There are also "temporary" SPI plans, in which the _SPI_plan struct is + * not even palloc'd but just exists in some function's local variable. + * The plancache entries are unsaved and exist under the SPI executor context, + * while additional data such as argtypes and list cells is loose in the SPI + * executor context. Such plans can be identified by having plancxt == NULL. * * Note: if the original query string contained only whitespace and comments, * the plancache_list will be NIL and so there is no place to store the diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index a4fb3b5f7f6..9998e2f24d6 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1996,7 +1996,10 @@ typedef struct SecLabelStmt #define CURSOR_OPT_NO_SCROLL 0x0004 /* NO SCROLL explicitly given */ #define CURSOR_OPT_INSENSITIVE 0x0008 /* INSENSITIVE */ #define CURSOR_OPT_HOLD 0x0010 /* WITH HOLD */ +/* these planner-control flags do not correspond to any SQL grammar: */ #define CURSOR_OPT_FAST_PLAN 0x0020 /* prefer fast-start plan */ +#define CURSOR_OPT_GENERIC_PLAN 0x0040 /* force use of generic plan */ +#define CURSOR_OPT_CUSTOM_PLAN 0x0080 /* force use of custom plan */ typedef struct DeclareCursorStmt { diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index 94c78289b6e..4796f5c7057 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -94,6 +94,7 @@ extern void MemoryContextSetParent(MemoryContext context, MemoryContext new_parent); extern Size GetMemoryChunkSpace(void *pointer); extern MemoryContext GetMemoryChunkContext(void *pointer); +extern MemoryContext MemoryContextGetParent(MemoryContext context); extern bool MemoryContextIsEmpty(MemoryContext context); extern void MemoryContextStats(MemoryContext context); diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index b8639a59a0e..c8c27bbb207 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -18,26 +18,47 @@ #include "access/tupdesc.h" #include "nodes/params.h" +#define CACHEDPLANSOURCE_MAGIC 195726186 +#define CACHEDPLAN_MAGIC 953717834 + /* - * CachedPlanSource represents the portion of a cached plan that persists - * across invalidation/replan cycles. It stores a raw parse tree (required), - * the original source text (also required, as of 8.4), and adjunct data. + * CachedPlanSource (which might better have been called CachedQuery) + * represents a SQL query that we expect to use multiple times. It stores + * the query source text, the raw parse tree, and the analyzed-and-rewritten + * query tree, as well as adjunct data. Cache invalidation can happen as a + * result of DDL affecting objects used by the query. In that case we discard + * the analyzed-and-rewritten query tree, and rebuild it when next needed. + * + * An actual execution plan, represented by CachedPlan, is derived from the + * CachedPlanSource when we need to execute the query. The plan could be + * either generic (usable with any set of plan parameters) or custom (for a + * specific set of parameters). plancache.c contains the logic that decides + * which way to do it for any particular execution. If we are using a generic + * cached plan then it is meant to be re-used across multiple executions, so + * callers must always treat CachedPlans as read-only. + * + * Once successfully built and "saved", CachedPlanSources typically live + * for the life of the backend, although they can be dropped explicitly. + * CachedPlans are reference-counted and go away automatically when the last + * reference is dropped. A CachedPlan can outlive the CachedPlanSource it + * was created from. * - * Normally, both the struct itself and the subsidiary data live in the - * context denoted by the context field, while the linked-to CachedPlan, if - * any, has its own context. Thus an invalidated CachedPlan can be dropped - * when no longer needed, and conversely a CachedPlanSource can be dropped - * without worrying whether any portals depend on particular instances of - * its plan. + * An "unsaved" CachedPlanSource can be used for generating plans, but it + * lives in transient storage and will not be updated in response to sinval + * events. * - * But for entries created by FastCreateCachedPlan, the CachedPlanSource - * and the initial version of the CachedPlan share the same memory context. - * In this case, we treat the memory context as belonging to the CachedPlan. - * The CachedPlanSource has an extra reference-counted link (orig_plan) - * to the CachedPlan, and the memory context goes away when the CachedPlan's - * reference count goes to zero. This arrangement saves overhead for plans - * that aren't expected to live long enough to need replanning, while not - * losing any flexibility if a replan turns out to be necessary. + * CachedPlans made from saved CachedPlanSources are likewise in permanent + * storage, so to avoid memory leaks, the reference-counted references to them + * must be held in permanent data structures or ResourceOwners. CachedPlans + * made from unsaved CachedPlanSources are in children of the caller's + * memory context, so references to them should not be longer-lived than + * that context. (Reference counting is somewhat pro forma in that case, + * though it may be useful if the CachedPlan can be discarded early.) + * + * A CachedPlanSource has two associated memory contexts: one that holds the + * struct itself, the query source text and the raw parse tree, and another + * context that holds the rewritten query tree and associated data. This + * allows the query tree to be discarded easily when it is invalidated. * * Note: the string referenced by commandTag is not subsidiary storage; * it is assumed to be a compile-time-constant string. As with portals, @@ -46,78 +67,93 @@ */ typedef struct CachedPlanSource { + int magic; /* should equal CACHEDPLANSOURCE_MAGIC */ Node *raw_parse_tree; /* output of raw_parser() */ - char *query_string; /* text of query (as of 8.4, never NULL) */ + char *query_string; /* source text of query */ const char *commandTag; /* command tag (a constant!), or NULL */ Oid *param_types; /* array of parameter type OIDs, or NULL */ int num_params; /* length of param_types array */ ParserSetupHook parserSetup; /* alternative parameter spec method */ void *parserSetupArg; int cursor_options; /* cursor options used for planning */ - bool fully_planned; /* do we cache planner or rewriter output? */ bool fixed_result; /* disallow change in result tupdesc? */ - struct OverrideSearchPath *search_path; /* saved search_path */ - int generation; /* counter, starting at 1, for replans */ TupleDesc resultDesc; /* result type; NULL = doesn't return tuples */ - struct CachedPlan *plan; /* link to plan, or NULL if not valid */ - MemoryContext context; /* context containing this CachedPlanSource */ - struct CachedPlan *orig_plan; /* link to plan owning my context */ + struct OverrideSearchPath *search_path; /* saved search_path */ + MemoryContext context; /* memory context holding all above */ + /* These fields describe the current analyzed-and-rewritten query tree: */ + List *query_list; /* list of Query nodes, or NIL if not valid */ + List *relationOids; /* OIDs of relations the queries depend on */ + List *invalItems; /* other dependencies, as PlanInvalItems */ + MemoryContext query_context; /* context holding the above, or NULL */ + /* If we have a generic plan, this is a reference-counted link to it: */ + struct CachedPlan *gplan; /* generic plan, or NULL if not valid */ + /* Some state flags: */ + bool is_complete; /* has CompleteCachedPlan been done? */ + bool is_saved; /* has CachedPlanSource been "saved"? */ + 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 */ + /* 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 */ } CachedPlanSource; /* - * CachedPlan represents the portion of a cached plan that is discarded when - * invalidation occurs. The reference count includes both the link(s) from the - * parent CachedPlanSource, and any active plan executions, so the plan can be - * discarded exactly when refcount goes to zero. Both the struct itself and - * the subsidiary data live in the context denoted by the context field. + * CachedPlan represents an execution plan derived from a CachedPlanSource. + * The reference count includes both the link from the parent CachedPlanSource + * (if any), and any active plan executions, so the plan can be discarded + * exactly when refcount goes to zero. Both the struct itself and the + * subsidiary data live in the context denoted by the context field. * This makes it easy to free a no-longer-needed cached plan. */ typedef struct CachedPlan { - List *stmt_list; /* list of statement or Query nodes */ - bool fully_planned; /* do we cache planner or rewriter output? */ - bool dead; /* if true, do not use */ + int magic; /* should equal CACHEDPLAN_MAGIC */ + List *stmt_list; /* list of statement nodes (PlannedStmts + * and bare utility statements) */ + bool is_saved; /* is CachedPlan in a long-lived context? */ + bool is_valid; /* is the stmt_list currently valid? */ TransactionId saved_xmin; /* if valid, replan when TransactionXmin * changes from this value */ + int generation; /* parent's generation number for this plan */ int refcount; /* count of live references to this struct */ - int generation; /* counter, starting at 1, for replans */ MemoryContext context; /* context containing this CachedPlan */ - /* These fields are used only in the not-fully-planned case: */ - List *relationOids; /* OIDs of relations the stmts depend on */ - List *invalItems; /* other dependencies, as PlanInvalItems */ } CachedPlan; extern void InitPlanCache(void); +extern void ResetPlanCache(void); + extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree, const char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, - bool fully_planned, - bool fixed_result); -extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree, - char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, - bool fully_planned, - bool fixed_result, - MemoryContext context); -extern void CachedPlanSetParserHook(CachedPlanSource *plansource, + const char *commandTag); +extern void CompleteCachedPlan(CachedPlanSource *plansource, + List *querytree_list, + MemoryContext querytree_context, + Oid *param_types, + int num_params, ParserSetupHook parserSetup, - void *parserSetupArg); + void *parserSetupArg, + int cursor_options, + bool fixed_result); + +extern void SaveCachedPlan(CachedPlanSource *plansource); extern void DropCachedPlan(CachedPlanSource *plansource); -extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource, - bool useResOwner); -extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner); + +extern void CachedPlanSetParentContext(CachedPlanSource *plansource, + MemoryContext newcontext); + +extern CachedPlanSource *CopyCachedPlan(CachedPlanSource *plansource); + extern bool CachedPlanIsValid(CachedPlanSource *plansource); -extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list); -extern void ResetPlanCache(void); +extern List *CachedPlanGetTargetList(CachedPlanSource *plansource); + +extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource, + ParamListInfo boundParams, + bool useResOwner); +extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner); #endif /* PLANCACHE_H */ |