Re: generic plans and "initial" pruning
Amit Langote <amitlangote09@gmail.com>
On Fri, Feb 11, 2022 at 7:02 AM Robert Haas <robertmhaas@gmail.com> wrote:
> On Thu, Feb 10, 2022 at 3:14 AM Amit Langote <amitlangote09@gmail.com> wrote:
> > Maybe this should be more than one patch? Say:
> >
> > 0001 to add ExecutorPrep and the boilerplate,
> > 0002 to teach plancache.c to use the new facility
Thanks for taking a look and sorry about the delay.
> Could be, not sure. I agree that if it's possible to split this in a
> meaningful way, it would facilitate review. I notice that there is
> some straight code movement e.g. the creation of
> ExecPartitionPruneFixSubPlanIndexes. It would be best, I think, to do
> pure code movement in a preparatory patch so that the main patch is
> just adding the new stuff we need and not moving stuff around.
Okay, created 0001 for moving around the execution pruning code.
> David Rowley recently proposed a patch for some parallel-safety
> debugging cross checks which added a plan tree walker. I'm not sure
> whether he's going to press that patch forward to commit, but I think
> we should get something like that into the tree and start using it,
> rather than adding more bespoke code. Maybe you/we should steal that
> part of his patch and commit it separately.
I looked at the thread you mentioned (I guess [1]), though it seems
David's proposing a path_tree_walker(), so I guess only useful within
the planner and not here.
> What I'm imagining is that
> plan_tree_walker() would know which nodes have subnodes and how to
> recurse over the tree structure, and you'd have a walker function to
> use with it that would know which executor nodes have ExecPrep
> functions and call them, and just do nothing for the others. That
> would spare you adding stub functions for nodes that don't need to do
> anything, or don't need to do anything other than recurse. Admittedly
> it would look a bit different from the existing executor phases, but
> I'd argue that it's a better coding model.
>
> Actually, you might've had this in the patch at some point, because
> you have a declaration for plan_tree_walker but no implementation.
Right, the previous patch indeed used a plan_tree_walker() for this
and I think in a way you seem to think it should work.
I do agree that plan_tree_walker() allows for a better implementation
of the idea of this patch and may also be generally useful, so I've
created a separate patch that adds it to nodeFuncs.c.
> I guess one thing that's a bit awkward about this idea is that in some
> cases you want to recurse to some subnodes but not other subnodes. But
> maybe it would work to put the recursion in the walker function in
> that case, and then just return true; but if you want to walk all
> children, return false.
Right, that's how I've made ExecPrepAppend() etc. do it.
> + bool contains_init_steps;
> + bool contains_exec_steps;
>
> s/steps/pruning/? maybe with contains -> needs or performs or requires as well?
Went with: needs_{init|exec}_pruning
> + * Returned information includes the set of RT indexes of relations referenced
> + * in the plan, and a PlanPrepOutput node for each node in the planTree if the
> + * node type supports producing one.
>
> Aren't all RT indexes referenced in the plan?
Ah yes. How about:
* Returned information includes the set of RT indexes of relations that must
* be locked to safely execute the plan,
> + * This may lock relations whose information may be used to produce the
> + * PlanPrepOutput nodes. For example, a partitioned table before perusing its
> + * PartitionPruneInfo contained in an Append node to do the pruning the result
> + * of which is used to populate the Append node's PlanPrepOutput.
>
> "may lock" feels awfully fuzzy to me. How am I supposed to rely on
> something that "may" happen? And don't we need to have tight logic
> around locking, with specific guarantees about what is locked at which
> points in the code and what is not?
Agree the wording was fuzzy. I've rewrote as:
* This locks relations whose information is needed to produce the
* PlanPrepOutput nodes. For example, a partitioned table before perusing its
* PartitionedRelPruneInfo contained in an Append node to do the pruning, the
* result of which is used to populate the Append node's PlanPrepOutput.
BTW, I've added an Assert in ExecGetRangeTableRelation():
/*
* A cross-check that AcquireExecutorLocks() hasn't missed any relations
* it must not have.
*/
Assert(estate->es_execprep == NULL ||
bms_is_member(rti, estate->es_execprep->relationRTIs));
which IOW ensures that the actual execution of a plan only sees
relations that ExecutorPrep() would've told AcquireExecutorLocks() to
take a lock on.
> + * At least one of 'planstate' or 'econtext' must be passed to be able to
> + * successfully evaluate any non-Const expressions contained in the
> + * steps.
>
> This also seems fuzzy. If I'm thinking of calling this function, I
> don't know how I'd know whether this criterion is met.
OK, I have removed this comment (which was on top of a static local
function) in favor of adding some commentary on this in places where
it belongs. For example, in ExecPrepDoInitialPruning():
/*
* We don't yet have a PlanState for the parent plan node, so must create
* a standalone ExprContext to evaluate pruning expressions, equipped with
* the information about the EXTERN parameters that the caller passed us.
* Note that that's okay because the initial pruning steps does not
* involve anything that requires the execution to have started.
*/
econtext = CreateStandaloneExprContext();
econtext->ecxt_param_list_info = params;
prunestate = ExecCreatePartitionPruneState(NULL, pruneinfo,
true, false,
rtable, econtext,
pdir, parentrelids);
> I don't love PlanPrepOutput the way you have it. I think one of the
> basic design issues for this patch is: should we think of the prep
> phase as specifically pruning, or is it general prep and pruning is
> the first thing for which we're going to use it? If it's really a
> pre-pruning phase, we could name it that way instead of calling it
> "prep". If it's really a general prep phase, then why does
> PlanPrepOutput contain initially_valid_subnodes as a field? One could
> imagine letting each prep function decide what kind of prep node it
> would like to return, with partition pruning being just one of the
> options. But is that a useful generalization of the basic concept, or
> just pretending that a special-purpose mechanism is more general than
> it really is?
While it can feel like the latter TBH, I'm inclined to keep
ExecutorPrep generalized. What bothers me about about the
alternative of calling the new phase something less generalized like
ExecutorDoInitPruning() is that that makes the somewhat elaborate API
changes needed for the phase's output to put into QueryDesc, through
which it ultimately reaches the main executor, seem less worthwhile.
I agree that PlanPrepOutput design needs to be likewise generalized,
maybe like you suggest -- using PlanInitPruningOutput, a child class
of PlanPrepOutput, to return the prep output for plan nodes that
support pruning.
Thoughts?
> + return CreateQueryDesc(pstmt, NULL, /* XXX pass ExecPrepOutput too? */
>
> It seems to me that we should do what the XXX suggests. It doesn't
> seem nice if the parallel workers could theoretically decide to prune
> a different set of nodes than the leader.
OK, will fix.
> + * known at executor startup (excludeing expressions containing
>
> Extra e.
>
> + * into subplan indexes, is also returned for use during subsquent
>
> Missing e.
Will fix.
> Somewhere, we're going to need to document the idea that this may
> permit us to execute a plan that isn't actually fully valid, but that
> we expect to survive because we'll never do anything with the parts of
> it that aren't. Maybe that should be added to the executor README, or
> maybe there's some better place, but I don't think that should remain
> something that's just implicit.
Agreed. I'd added a description of the new prep phase to executor
README, though the text didn't mention this particular bit. Will fix
to mention it.
> This is not a full review, just some initial thoughts looking through this.
Thanks again. Will post a new version soon after a bit more polishing.
--
Amit Langote
EDB: http://www.enterprisedb.com
[1] https://www.postgresql.org/message-id/flat/b59605fecb20ba9ea94e70ab60098c237c870628.camel%40postgrespro.ru
Commits
-
Stamp 19beta1.
- 4b0bf0788b06 19 (unreleased) cited
-
Revert "Don't lock partitions pruned by initial pruning"
- 1722d5eb05d8 18.0 landed
-
Ensure first ModifyTable rel initialized if all are pruned
- 28317de723b6 18.0 cited
-
Fix bug in cbc127917 to handle nested Append correctly
- cbb9086c9ef6 18.0 landed
-
Remove unstable test suite added by 525392d57
- 4f1b6e5bb4fe 18.0 landed
-
Don't lock partitions pruned by initial pruning
- 525392d5727f 18.0 landed
-
Fix an oversight in cbc127917 to handle MERGE correctly
- 75dfde13639a 18.0 landed
-
Track unpruned relids to avoid processing pruned relations
- cbc127917e04 18.0 landed
-
Perform runtime initial pruning outside ExecInitNode()
- d47cbf474ecb 18.0 landed
-
Move PartitionPruneInfo out of plan nodes into PlannedStmt
- bb3ec16e14de 18.0 landed
-
Fix setrefs.c's failure to do expression processing on prune steps.
- bf826ea06297 18.0 cited
-
Remove obsolete executor cleanup code
- d060e921ea5a 17.0 landed
-
Revert "Move PartitionPruneInfo out of plan nodes into PlannedStmt"
- 5472743d9e85 16.0 landed
-
Move PartitioPruneInfo out of plan nodes into PlannedStmt
- ec386948948c 16.0 landed
-
Refactor and cleanup runtime partition prune code a little
- 297daa9d4353 15.0 landed
-
Remove some unnecessary fields from Plan trees.
- 52ed730d511b 12.0 cited
-
Remove more redundant relation locking during executor startup.
- f2343653f5b2 12.0 cited
-
Shut down Gather's children before shutting down Gather itself.
- acf555bc53ac 10.0 cited