diff options
author | Andres Freund <andres@anarazel.de> | 2019-09-05 13:00:20 -0700 |
---|---|---|
committer | Andres Freund <andres@anarazel.de> | 2019-09-09 05:14:11 -0700 |
commit | 27cc7cd2bc8a5e8efc8279bc5d2a8ae42fd8ad33 (patch) | |
tree | 090b7fb7a079d651ddab923befdcf8755f23e40e /src/include/nodes/execnodes.h | |
parent | 7e04160390464cd39690d36054e0ac5e4f1bf227 (diff) |
Reorder EPQ work, to fix rowmark related bugs and improve efficiency.
In ad0bda5d24ea I changed the EvalPlanQual machinery to store
substitution tuples in slot, instead of using plain HeapTuples. The
main motivation for that was that using HeapTuples will be inefficient
for future tableams. But it turns out that that conversion was buggy
for non-locking rowmarks - the wrong tuple descriptor was used to
create the slot.
As a secondary issue 5db6df0c0 changed ExecLockRows() to begin EPQ
earlier, to allow to fetch the locked rows directly into the EPQ
slots, instead of having to copy tuples around. Unfortunately, as Tom
complained, that forces some expensive initialization to happen
earlier.
As a third issue, the test coverage for EPQ was clearly insufficient.
Fixing the first issue is unfortunately not trivial: Non-locked row
marks were fetched at the start of EPQ, and we don't have the type
information for the rowmarks available at that point. While we could
change that, it's not easy. It might be worthwhile to change that at
some point, but to fix this bug, it seems better to delay fetching
non-locking rowmarks when they're actually needed, rather than
eagerly. They're referenced at most once, and in cases where EPQ
fails, might never be referenced. Fetching them when needed also
increases locality a bit.
To be able to fetch rowmarks during execution, rather than
initialization, we need to be able to access the active EPQState, as
that contains necessary data. To do so move EPQ related data from
EState to EPQState, and, only for EStates creates as part of EPQ,
reference the associated EPQState from EState.
To fix the second issue, change EPQ initialization to allow use of
EvalPlanQualSlot() to be used before EvalPlanQualBegin() (but
obviously still requiring EvalPlanQualInit() to have been done).
As these changes made struct EState harder to understand, e.g. by
adding multiple EStates, significantly reorder the members, and add a
lot more comments.
Also add a few more EPQ tests, including one that fails for the first
issue above. More is needed.
Reported-By: yi huang
Author: Andres Freund
Reviewed-By: Tom Lane
Discussion:
https://postgr.es/m/CAHU7rYZo_C4ULsAx_LAj8az9zqgrD8WDd4hTegDTMM1LMqrBsg@mail.gmail.com
https://postgr.es/m/24530.1562686693@sss.pgh.pa.us
Backpatch: 12-, where the EPQ changes were introduced
Diffstat (limited to 'src/include/nodes/execnodes.h')
-rw-r--r-- | src/include/nodes/execnodes.h | 83 |
1 files changed, 67 insertions, 16 deletions
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index f42189d2bf6..b593d22c48a 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -571,17 +571,12 @@ typedef struct EState ExprContext *es_per_tuple_exprcontext; /* - * These fields are for re-evaluating plan quals when an updated tuple is - * substituted in READ COMMITTED mode. es_epqTupleSlot[] contains test - * tuples that scan plan nodes should return instead of whatever they'd - * normally return, or an empty slot if there is nothing to return; if - * es_epqTupleSlot[] is not NULL if a particular array entry is valid; and - * es_epqScanDone[] is state to remember if the tuple has been returned - * already. Arrays are of size es_range_table_size and are indexed by - * scan node scanrelid - 1. + * If not NULL, this is an EPQState's EState. This is a field in EState + * both to allow EvalPlanQual aware executor nodes to detect that they + * need to perform EPQ related work, and to provide necessary information + * to do so. */ - TupleTableSlot **es_epqTupleSlot; /* array of EPQ substitute tuples */ - bool *es_epqScanDone; /* true if EPQ tuple has been fetched */ + struct EPQState *es_epq_active; bool es_use_parallel_mode; /* can we use parallel workers? */ @@ -1057,17 +1052,73 @@ typedef struct PlanState /* * EPQState is state for executing an EvalPlanQual recheck on a candidate - * tuple in ModifyTable or LockRows. The estate and planstate fields are - * NULL if inactive. + * tuples e.g. in ModifyTable or LockRows. + * + * To execute EPQ a separate EState is created (stored in ->recheckestate), + * which shares some resources, like the rangetable, with the main query's + * EState (stored in ->parentestate). The (sub-)tree of the plan that needs to + * be rechecked (in ->plan), is separately initialized (into + * ->recheckplanstate), but shares plan nodes with the corresponding nodes in + * the main query. The scan nodes in that separate executor tree are changed + * to return only the current tuple of interest for the respective + * table. Those tuples are either provided by the caller (using + * EvalPlanQualSlot), and/or found using the rowmark mechanism (non-locking + * rowmarks by the EPQ machinery itself, locking ones by the caller). + * + * While the plan to be checked may be changed using EvalPlanQualSetPlan() - + * e.g. so all source plans for a ModifyTable node can be processed - all such + * plans need to share the same EState. */ typedef struct EPQState { - EState *estate; /* subsidiary EState */ - PlanState *planstate; /* plan state tree ready to be executed */ - TupleTableSlot *origslot; /* original output tuple to be rechecked */ + /* Initialized at EvalPlanQualInit() time: */ + + EState *parentestate; /* main query's EState */ + int epqParam; /* ID of Param to force scan node re-eval */ + + /* + * Tuples to be substituted by scan nodes. They need to set up, before + * calling EvalPlanQual()/EvalPlanQualNext(), into the slot returned by + * EvalPlanQualSlot(scanrelid). The array is indexed by scanrelid - 1. + */ + List *tuple_table; /* tuple table for relsubs_slot */ + TupleTableSlot **relsubs_slot; + + /* + * Initialized by EvalPlanQualInit(), may be changed later with + * EvalPlanQualSetPlan(): + */ + Plan *plan; /* plan tree to be executed */ List *arowMarks; /* ExecAuxRowMarks (non-locking only) */ - int epqParam; /* ID of Param to force scan node re-eval */ + + + /* + * The original output tuple to be rechecked. Set by + * EvalPlanQualSetSlot(), before EvalPlanQualNext() or EvalPlanQual() may + * be called. + */ + TupleTableSlot *origslot; + + + /* Initialized or reset by EvalPlanQualBegin(): */ + + EState *recheckestate; /* EState for EPQ execution, see above */ + + /* + * Rowmarks that can be fetched on-demand using + * EvalPlanQualFetchRowMark(), indexed by scanrelid - 1. Only non-locking + * rowmarks. + */ + ExecAuxRowMark **relsubs_rowmark; + + /* + * True if a relation's EPQ tuple has been fetched for relation, indexed + * by scanrelid - 1. + */ + bool *relsubs_done; + + PlanState *recheckplanstate; /* EPQ specific exec nodes, for ->plan */ } EPQState; |