Re: ModifyTable overheads in generic plans

From: Heikki Linnakangas <hlinnaka(at)iki(dot)fi>
To: Amit Langote <amitlangote09(at)gmail(dot)com>
Cc: Robert Haas <robertmhaas(at)gmail(dot)com>, PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org>, David Rowley <dgrowleyml(at)gmail(dot)com>, "Tsunakawa, Takayuki" <tsunakawa(dot)takay(at)jp(dot)fujitsu(dot)com>
Subject: Re: ModifyTable overheads in generic plans
Date: 2020-11-11 13:14:58
Message-ID: ac2b8afd-e51f-e61b-1a41-ea675d480cc8@iki.fi
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

I'm still a bit confused and unhappy about the initialization of
ResultRelInfos and the various fields in them. We've made progress in
the previous patches, but it's still a bit messy.

> /*
> * If transition tuples will be captured, initialize a map to convert
> * child tuples into the format of the table mentioned in the query
> * (root relation), because the transition tuple store can only store
> * tuples in the root table format. However for INSERT, the map is
> * only initialized for a given partition when the partition itself is
> * first initialized by ExecFindPartition. Also, this map is also
> * needed if an UPDATE ends up having to move tuples across
> * partitions, because in that case the child tuple to be moved first
> * needs to be converted into the root table's format. In that case,
> * we use GetChildToRootMap() to either create one from scratch if
> * we didn't already create it here.
> *
> * Note: We cannot always initialize this map lazily, that is, use
> * GetChildToRootMap(), because AfterTriggerSaveEvent(), which needs
> * the map, doesn't have access to the "target" relation that is
> * needed to create the map.
> */
> if (mtstate->mt_transition_capture && operation != CMD_INSERT)
> {
> Relation relation = resultRelInfo->ri_RelationDesc;
> Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
>
> resultRelInfo->ri_ChildToRootMap =
> convert_tuples_by_name(RelationGetDescr(relation),
> RelationGetDescr(targetRel));
> /* First time creating the map for this result relation. */
> Assert(!resultRelInfo->ri_ChildToRootMapValid);
> resultRelInfo->ri_ChildToRootMapValid = true;
> }

The comment explains that AfterTriggerSaveEvent() cannot use
GetChildToRootMap(), because it doesn't have access to the root target
relation. But there is a field for that in ResultRelInfo:
ri_PartitionRoot. However, that's only set up when we do partition routing.

How about we rename ri_PartitionRoot to e.g ri_RootTarget, and set it
always, even for non-partition inheritance? We have that information
available when we initialize the ResultRelInfo, so might as well.

Some code currently checks ri_PartitionRoot, to determine if a tuple
that's been inserted, has been routed. For example:

> /*
> * Also check the tuple against the partition constraint, if there is
> * one; except that if we got here via tuple-routing, we don't need to
> * if there's no BR trigger defined on the partition.
> */
> if (resultRelationDesc->rd_rel->relispartition &&
> (resultRelInfo->ri_PartitionRoot == NULL ||
> (resultRelInfo->ri_TrigDesc &&
> resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
> ExecPartitionCheck(resultRelInfo, slot, estate, true);

So if we set ri_PartitionRoot always, we would need some other way to
determine if the tuple at hand has actually been routed or not. But
wouldn't that be a good thing anyway? Isn't it possible that the same
ResultRelInfo is sometimes used for routed tuples, and sometimes for
tuples that have been inserted/updated "directly"?
ExecLookupUpdateResultRelByOid() sets that field lazily, so I think it
would be possible to get here with ri_PartitionRoot either set or not,
depending on whether an earlier cross-partition update was routed to the
table.

The above check is just an optimization, to skip unnecessary
ExecPartitionCheck() calls, but I think this snippet in
ExecConstraints() needs to get this right:

> /*
> * If the tuple has been routed, it's been converted to the
> * partition's rowtype, which might differ from the root
> * table's. We must convert it back to the root table's
> * rowtype so that val_desc shown error message matches the
> * input tuple.
> */
> if (resultRelInfo->ri_PartitionRoot)
> {
> AttrMap *map;
>
> rel = resultRelInfo->ri_PartitionRoot;
> tupdesc = RelationGetDescr(rel);
> /* a reverse map */
> map = build_attrmap_by_name_if_req(orig_tupdesc,
> tupdesc);
>
> /*
> * Partition-specific slot's tupdesc can't be changed, so
> * allocate a new one.
> */
> if (map != NULL)
> slot = execute_attr_map_slot(map, slot,
> MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
> }

Is that an existing bug, or am I missing?

- Heikki

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Greg Sabino Mullane 2020-11-11 13:21:31 Re: Prefer TG_TABLE_NAME over TG_RELNAME in tests
Previous Message Peter Eisentraut 2020-11-11 12:40:31 Re: Clean up optional rules in grammar