Re: executor relation handling

From: Amit Langote <Langote_Amit_f8(at)lab(dot)ntt(dot)co(dot)jp>
To: David Rowley <david(dot)rowley(at)2ndquadrant(dot)com>
Cc: Pg Hackers <pgsql-hackers(at)postgresql(dot)org>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Subject: Re: executor relation handling
Date: 2018-09-12 05:23:57
Message-ID: 4f1565de-e593-28ad-53d7-3a70f0d1241b@lab.ntt.co.jp
Views: Raw Message | Whole Thread | Download mbox
Thread:
Lists: pgsql-hackers

Thank you for reviewing.

On 2018/09/10 13:36, David Rowley wrote:
> On 4 September 2018 at 20:53, Amit Langote
> <Langote_Amit_f8(at)lab(dot)ntt(dot)co(dot)jp> wrote:
>> Updated patches attached; 0001-0003 are same as v1.
>
> I've looked at these. Here's my review so far:
>
> 0001:
>
> 1. The following does not seem to be true any longer:
>
> + /*
> + * If you change the conditions under which rel locks are acquired
> + * here, be sure to adjust ExecOpenScanRelation to match.
> + */
>
> per:
>
> @@ -652,28 +654,10 @@ ExecOpenScanRelation(EState *estate, Index
> scanrelid, int eflags)
> {
> Relation rel;
> Oid reloid;
> - LOCKMODE lockmode;
>
> - /*
> - * Determine the lock type we need. First, scan to see if target relation
> - * is a result relation. If not, check if it's a FOR UPDATE/FOR SHARE
> - * relation. In either of those cases, we got the lock already.
> - */
> - lockmode = AccessShareLock;
> - if (ExecRelationIsTargetRelation(estate, scanrelid))
> - lockmode = NoLock;
> - else
> - {
> - /* Keep this check in sync with InitPlan! */
> - ExecRowMark *erm = ExecFindRowMark(estate, scanrelid, true);
> -
> - if (erm != NULL && erm->relation != NULL)
> - lockmode = NoLock;
> - }
> -

Yeah, removed that comment in ExecBuildRowMark.

> 2. Should addRangeTableEntryForRelation() initialize lockmode, or
> maybe take it as a parameter?

Hmm, that sounds like a good idea. Looking at the various places it's
called from though, it's not clear in many instances which lock was taken
on the relation that's passed to it, because the lock itself seems to be
taken several stack frames removed from the call site.

That said, at least for the cases that we care about, that is, the cases
in which the RTE being built will be passed to the executor by the way of
being in a PlannedStmt, it's clear which lock is taken. So, we can add
the lockmode parameter to addRangeTableEntryForRelation as you suggest and
pass the actual lockmode value only in the cases we care about, mentioning
the fact in the function's header comment that callers may pass NoLock
arbitrarily if it's clear the RTE won't be passed to the executor.

I've modified patch that way. Thoughts?

> 3. Don't think there's a need to capitalise true and false in:
>
> + * Return TRUE if we acquired a new lock, FALSE if already held.

OK, fixed.

> 4. The comment probably should read "lock level to obtain, or 0 if no
> lock is required" in:
>
> + int lockmode; /* lock taken on the relation or 0 */

OK.

> The field should likely also be LOCKMODE, not int.

OK.

> 5. AcquireExecutorLocks() does not need the local variable named rt_index

Good catch, removed.

> 0002:
>
> 6. I don't think "rootRelation" is a good name for this field. I think
> "root" is being confused with "target". Nothing is it say the target
> is the same as the root.
>
> + Index rootRelation; /* RT index of root partitioned table */
>
> Perhaps "partitionedTarget" is a better name?

I realized that we don't need a new Index field here. nominalRelation
serves the purpose that rootRelation is meant for, so it seems silly to
have two fields of the same value. Instead, let's have a bool
partitionedTarget which is set to true if the target (whose RT index is
nominalRelation) is a partitioned tables.

> 7. Should use "else" instead of "else if" in:
>
> + /* Top-level Plan must be LockRows or ModifyTable */
> + Assert(IsA(stmt->planTree, LockRows) ||
> + IsA(stmt->planTree, ModifyTable));
> + if (IsA(stmt->planTree, LockRows))
> + rowMarks = ((LockRows *) stmt->planTree)->rowMarks;
> + else if (IsA(stmt->planTree, ModifyTable))
> + rowMarks = ((ModifyTable *) stmt->planTree)->rowMarks;
>
> or you'll likely get a compiler warning on non-Assert enabled builds.

Yep, fixed.

> 0003:
>
> 8. The following code seems repeated enough to warrant a static function:
>
> + rowMarks = NIL;
> + foreach(lc, root->rowMarks)
> + {
> + PlanRowMark *rc = lfirst(lc);
> +
> + if (root->simple_rel_array[rc->rti] != NULL &&
> + IS_DUMMY_REL(root->simple_rel_array[rc->rti]))
> + continue;
> +
> + rowMarks = lappend(rowMarks, rc);
> + }
>
> Also, why not reverse the condition and do the lappend inside the if?
> Save two lines.

OK, made this change and added a static function called
get_unpruned_rowmarks(PlannerInfo *root).

>
> 9. The following code appears in copy.c, which is pretty much the same
> as the code in execMain.c:
>
> estate->es_range_table_size = list_length(cstate->range_table);
> estate->es_range_table_array = (RangeTblEntry **)
> palloc(sizeof(RangeTblEntry *) *
> estate->es_range_table_size);
> /* Populate the range table array */
> i = 0;
> foreach(lc, cstate->range_table)
> estate->es_range_table_array[i++] = lfirst_node(RangeTblEntry, lc);
>
> Would it not be better to invent a function with the signature:
>
> void
> setup_range_table_array(EState *estate, List *rangeTable)
>
> and use it in both locations?

Agreed, but I named it ExecInitRangeTable.

> 10. In create_estate_for_relation() I don't think you should remove
> the line that sets es_range_table.
>
> @@ -199,7 +199,8 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
> rte->rtekind = RTE_RELATION;
> rte->relid = RelationGetRelid(rel->localrel);
> rte->relkind = rel->localrel->rd_rel->relkind;
> - estate->es_range_table = list_make1(rte);
>
> If you're keeping es_range_table then I think it needs to always be
> set properly to help prevent future bugs in that area.

My bad, fixed.

> 11. ExecRangeTableRelation should look more like:
>
> ExecRangeTableRelation(EState *estate, Index rti)
> {
> Relation rel = estate->es_relations[rti - 1];
>
> if (rel != NULL)
> RelationIncrementReferenceCount(rel);
> else
> {
> RangeTblEntry *rte = exec_rt_fetch(rti, estate->es_range_table_array);
>
> /*
> * No need to lock the relation lock, because upstream code
> * must hold the lock already.
> */
> rel = estate->es_relations[rti - 1] = heap_open(rte->relid, NoLock);
> }
>
> return rel;
> }

Much better, done.

> 12. I think this should read: /* Fill in RTEs. es_relations will be
> populated later. */
>
> + /* Fill the RTEs, Relations array will be filled later. */

I've rewritten the comment.

> 13. I also notice that you're still cleaning up Relations with
> heap_close or relation_close. Did you consider not doing that and just
> having a function that's run at the end of execution which closes all
> non-NULL es_relations? This way you'd not need to perform
> RelationIncrementReferenceCount inside ExecRangeTableRelation.

Agreed, done. I'd be slightly hesitant to remove ExecCloseScanRelation,
ExecDestroyPartitionPruneState et al if they weren't just a wrapper around
heap_close.

Please find attached revised patches.

Thanks,
Amit

Attachment Content-Type Size
v3-0001-Don-t-lock-range-table-relations-in-the-executor.patch text/plain 41.3 KB
v3-0002-Remove-useless-fields-from-planner-nodes.patch text/plain 36.8 KB
v3-0003-Prune-PlanRowMark-of-relations-that-are-pruned-fr.patch text/plain 2.4 KB
v3-0004-Revise-executor-range-table-relation-opening-clos.patch text/plain 34.6 KB

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Michael Paquier 2018-09-12 05:43:02 Re: Loaded footgun open_datasync on Windows
Previous Message Ideriha, Takeshi 2018-09-12 05:16:52 RE: Protect syscache from bloating with negative cache entries