Re: ModifyTable overheads in generic plans

Heikki Linnakangas <hlinnaka@iki.fi>

From: Heikki Linnakangas <hlinnaka@iki.fi>
To: Amit Langote <amitlangote09@gmail.com>
Cc: Robert Haas <robertmhaas@gmail.com>, PostgreSQL-development <pgsql-hackers@postgresql.org>, David Rowley <dgrowleyml@gmail.com>, "Tsunakawa, Takayuki" <tsunakawa.takay@jp.fujitsu.com>
Date: 2020-11-11T13:14:58Z
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



Commits

  1. Comment cleanup for a1115fa07.

  2. Postpone some more stuff out of ExecInitModifyTable.

  3. Postpone some stuff out of ExecInitModifyTable.