From 277cb789836b5ddf81aabb80c2058268c70e2f36 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 6 Mar 2019 15:43:33 -0800 Subject: Don't reuse slots between root and partition in ON CONFLICT ... UPDATE. Until now the the slot to store the conflicting tuple, and the result of the ON CONFLICT SET, where reused between partitions. That necessitated changing slots descriptor when switching partitions. Besides the overhead of switching descriptors on a slot (which requires memory allocations and prevents JITing), that's importantly also problematic for tableam. There individual partitions might belong to different tableams, needing different kinds of slots. In passing also fix ExecOnConflictUpdate to clear the existing slot at exit. Otherwise that slot could continue to hold a pin till the query ends, which could be far too long if the input data set is large, and there's no further conflicts. While previously also problematic, it's now more important as there will be more such slots when partitioned. Author: Andres Freund Reviewed-By: Robert Haas, David Rowley Discussion: https://postgr.es/m/20180703070645.wchpu5muyto5n647@alap3.anarazel.de --- src/backend/executor/nodeModifyTable.c | 70 ++++++++++++---------------------- 1 file changed, 24 insertions(+), 46 deletions(-) (limited to 'src/backend/executor/nodeModifyTable.c') diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 14d25fd2aa8..b9bd86ff8fd 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1304,6 +1304,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, ExprContext *econtext = mtstate->ps.ps_ExprContext; Relation relation = resultRelInfo->ri_RelationDesc; ExprState *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause; + TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing; HeapTupleData tuple; HeapUpdateFailureData hufd; LockTupleMode lockmode; @@ -1413,7 +1414,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, ExecCheckHeapTupleVisible(estate, &tuple, buffer); /* Store target's existing tuple in the state's dedicated slot */ - ExecStoreBufferHeapTuple(&tuple, mtstate->mt_existing, buffer); + ExecStorePinnedBufferHeapTuple(&tuple, existing, buffer); /* * Make tuple and any needed join variables available to ExecQual and @@ -1422,13 +1423,13 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * has been made to reference INNER_VAR in setrefs.c, but there is no * other redirection. */ - econtext->ecxt_scantuple = mtstate->mt_existing; + econtext->ecxt_scantuple = existing; econtext->ecxt_innertuple = excludedSlot; econtext->ecxt_outertuple = NULL; if (!ExecQual(onConflictSetWhere, econtext)) { - ReleaseBuffer(buffer); + ExecClearTuple(existing); /* see return below */ InstrCountFiltered1(&mtstate->ps, 1); return true; /* done with the tuple */ } @@ -1451,7 +1452,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * INSERT or UPDATE path. */ ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo, - mtstate->mt_existing, + existing, mtstate->ps.state); } @@ -1469,11 +1470,17 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, /* Execute UPDATE with projection */ *returning = ExecUpdate(mtstate, &tuple.t_self, NULL, - mtstate->mt_conflproj, planSlot, + resultRelInfo->ri_onConflict->oc_ProjSlot, + planSlot, &mtstate->mt_epqstate, mtstate->ps.state, canSetTag); - ReleaseBuffer(buffer); + /* + * Clear out existing tuple, as there might not be another conflict among + * the next input rows. Don't want to hold resources till the end of the + * query. + */ + ExecClearTuple(existing); return true; } @@ -1633,7 +1640,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate, ResultRelInfo *targetRelInfo, TupleTableSlot *slot) { - ModifyTable *node; ResultRelInfo *partrel; PartitionRoutingInfo *partrouteinfo; TupleConversionMap *map; @@ -1698,19 +1704,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate, slot = execute_attr_map_slot(map->attrMap, slot, new_slot); } - /* Initialize information needed to handle ON CONFLICT DO UPDATE. */ - Assert(mtstate != NULL); - node = (ModifyTable *) mtstate->ps.plan; - if (node->onConflictAction == ONCONFLICT_UPDATE) - { - Assert(mtstate->mt_existing != NULL); - ExecSetSlotDescriptor(mtstate->mt_existing, - RelationGetDescr(partrel->ri_RelationDesc)); - Assert(mtstate->mt_conflproj != NULL); - ExecSetSlotDescriptor(mtstate->mt_conflproj, - partrel->ri_onConflict->oc_ProjTupdesc); - } - return slot; } @@ -2319,43 +2312,28 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) econtext = mtstate->ps.ps_ExprContext; relationDesc = resultRelInfo->ri_RelationDesc->rd_att; - /* - * Initialize slot for the existing tuple. If we'll be performing - * tuple routing, the tuple descriptor to use for this will be - * determined based on which relation the update is actually applied - * to, so we don't set its tuple descriptor here. - */ - mtstate->mt_existing = - ExecInitExtraTupleSlot(mtstate->ps.state, - mtstate->mt_partition_tuple_routing ? - NULL : relationDesc, &TTSOpsBufferHeapTuple); - /* carried forward solely for the benefit of explain */ mtstate->mt_excludedtlist = node->exclRelTlist; /* create state for DO UPDATE SET operation */ resultRelInfo->ri_onConflict = makeNode(OnConflictSetState); - /* - * Create the tuple slot for the UPDATE SET projection. - * - * Just like mt_existing above, we leave it without a tuple descriptor - * in the case of partitioning tuple routing, so that it can be - * changed by ExecPrepareTupleRouting. In that case, we still save - * the tupdesc in the parent's state: it can be reused by partitions - * with an identical descriptor to the parent. - */ + /* initialize slot for the existing tuple */ + resultRelInfo->ri_onConflict->oc_Existing = + ExecInitExtraTupleSlot(mtstate->ps.state, relationDesc, + &TTSOpsBufferHeapTuple); + + /* create the tuple slot for the UPDATE SET projection */ tupDesc = ExecTypeFromTL((List *) node->onConflictSet); - mtstate->mt_conflproj = - ExecInitExtraTupleSlot(mtstate->ps.state, - mtstate->mt_partition_tuple_routing ? - NULL : tupDesc, &TTSOpsHeapTuple); - resultRelInfo->ri_onConflict->oc_ProjTupdesc = tupDesc; + resultRelInfo->ri_onConflict->oc_ProjSlot = + ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc, + &TTSOpsVirtual); /* build UPDATE SET projection state */ resultRelInfo->ri_onConflict->oc_ProjInfo = ExecBuildProjectionInfo(node->onConflictSet, econtext, - mtstate->mt_conflproj, &mtstate->ps, + resultRelInfo->ri_onConflict->oc_ProjSlot, + &mtstate->ps, relationDesc); /* initialize state to evaluate the WHERE clause, if any */ -- cgit v1.2.3