Files
postgresql/src/include/executor/spi_priv.h
Tom Lane ee895a655c Improve performance of repeated CALLs within plpgsql procedures.
This patch essentially is cleaning up technical debt left behind
by the original implementation of plpgsql procedures, particularly
commit d92bc83c4.  That patch (or more precisely, follow-on patches
fixing its worst bugs) forced us to re-plan CALL and DO statements
each time through, if we're in a non-atomic context.  That wasn't
for any fundamental reason, but just because use of a saved plan
requires having a ResourceOwner to hold a reference count for the
plan, and we had no suitable resowner at hand, nor would the
available APIs support using one if we did.  While it's not that
expensive to create a "plan" for CALL/DO, the cycles do add up
in repeated executions.

This patch therefore makes the following API changes:

* GetCachedPlan/ReleaseCachedPlan are modified to let the caller
specify which resowner to use to pin the plan, rather than forcing
use of CurrentResourceOwner.

* spi.c gains a "SPI_execute_plan_extended" entry point that lets
callers say which resowner to use to pin the plan.  This borrows the
idea of an options struct from the recently added SPI_prepare_extended,
hopefully allowing future options to be added without more API breaks.
This supersedes SPI_execute_plan_with_paramlist (which I've marked
deprecated) as well as SPI_execute_plan_with_receiver (which is new
in v14, so I just took it out altogether).

* I also took the opportunity to remove the crude hack of letting
plpgsql reach into SPI private data structures to mark SPI plans as
"no_snapshot".  It's better to treat that as an option of
SPI_prepare_extended.

Now, when running a non-atomic procedure or DO block that contains
any CALL or DO commands, plpgsql creates a ResourceOwner that
will be used to pin the plans of the CALL/DO commands.  (In an
atomic context, we just use CurrentResourceOwner, as before.)
Having done this, we can just save CALL/DO plans normally,
whether or not they are used across transaction boundaries.
This seems to be good for something like 2X speedup of a CALL
of a trivial procedure with a few simple argument expressions.
By restricting the creation of an extra ResourceOwner like this,
there's essentially zero penalty in cases that can't benefit.

Pavel Stehule, with some further hacking by me

Discussion: https://postgr.es/m/CAFj8pRCLPdDAETvR7Po7gC5y_ibkn_-bOzbeJb39WHms01194Q@mail.gmail.com
2021-01-25 22:28:29 -05:00

106 lines
4.3 KiB
C

/*-------------------------------------------------------------------------
*
* spi_priv.h
* Server Programming Interface private declarations
*
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/executor/spi_priv.h
*
*-------------------------------------------------------------------------
*/
#ifndef SPI_PRIV_H
#define SPI_PRIV_H
#include "executor/spi.h"
#include "utils/queryenvironment.h"
#define _SPI_PLAN_MAGIC 569278163
typedef struct
{
/* current results */
uint64 processed; /* by Executor */
SPITupleTable *tuptable; /* tuptable currently being built */
/* subtransaction in which current Executor call was started */
SubTransactionId execSubid;
/* resources of this execution context */
slist_head tuptables; /* list of all live SPITupleTables */
MemoryContext procCxt; /* procedure context */
MemoryContext execCxt; /* executor context */
MemoryContext savedcxt; /* context of SPI_connect's caller */
SubTransactionId connectSubid; /* ID of connecting subtransaction */
QueryEnvironment *queryEnv; /* query environment setup for SPI level */
/* transaction management support */
bool atomic; /* atomic execution context, does not allow
* transactions */
bool internal_xact; /* SPI-managed transaction boundary, skip
* cleanup */
/* saved values of API global variables for previous nesting level */
uint64 outer_processed;
SPITupleTable *outer_tuptable;
int outer_result;
} _SPI_connection;
/*
* SPI plans have three states: saved, unsaved, or temporary.
*
* 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).
*
* 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.
*
* 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.
*
* We can also have "one-shot" SPI plans (which are typically temporary,
* as described above). These are meant to be executed once and discarded,
* and various optimizations are made on the assumption of single use.
* Note in particular that the CachedPlanSources within such an SPI plan
* are not "complete" until execution.
*
* 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
* query string. We don't care about that, but we do care about the
* argument type array, which is why it's seemingly-redundantly stored.
*/
typedef struct _SPI_plan
{
int magic; /* should equal _SPI_PLAN_MAGIC */
bool saved; /* saved or unsaved plan? */
bool oneshot; /* one-shot plan? */
List *plancache_list; /* one CachedPlanSource per parsetree */
MemoryContext plancxt; /* Context containing _SPI_plan and data */
RawParseMode parse_mode; /* raw_parser() mode */
int cursor_options; /* Cursor options used for planning */
int nargs; /* number of plan arguments */
Oid *argtypes; /* Argument types (NULL if nargs is 0) */
ParserSetupHook parserSetup; /* alternative parameter spec method */
void *parserSetupArg;
} _SPI_plan;
#endif /* SPI_PRIV_H */