Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 277cb78

Browse files
committed
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
1 parent d16a74c commit 277cb78

File tree

3 files changed

+64
-63
lines changed

3 files changed

+64
-63
lines changed

src/backend/executor/execPartition.c

+38-14
Original file line numberDiff line numberDiff line change
@@ -723,28 +723,55 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
723723
if (node->onConflictAction == ONCONFLICT_UPDATE)
724724
{
725725
TupleConversionMap *map;
726+
TupleDesc leaf_desc;
726727

727728
map = leaf_part_rri->ri_PartitionInfo->pi_RootToPartitionMap;
729+
leaf_desc = RelationGetDescr(leaf_part_rri->ri_RelationDesc);
728730

729731
Assert(node->onConflictSet != NIL);
730732
Assert(rootResultRelInfo->ri_onConflict != NULL);
731733

734+
leaf_part_rri->ri_onConflict = makeNode(OnConflictSetState);
735+
736+
/*
737+
* Need a separate existing slot for each partition, as the
738+
* partition could be of a different AM, even if the tuple
739+
* descriptors match.
740+
*/
741+
leaf_part_rri->ri_onConflict->oc_Existing =
742+
ExecInitExtraTupleSlot(mtstate->ps.state,
743+
leaf_desc,
744+
&TTSOpsBufferHeapTuple);
745+
732746
/*
733747
* If the partition's tuple descriptor matches exactly the root
734-
* parent (the common case), we can simply re-use the parent's ON
748+
* parent (the common case), we can re-use most of the parent's ON
735749
* CONFLICT SET state, skipping a bunch of work. Otherwise, we
736750
* need to create state specific to this partition.
737751
*/
738752
if (map == NULL)
739-
leaf_part_rri->ri_onConflict = rootResultRelInfo->ri_onConflict;
753+
{
754+
/*
755+
* It's safe to reuse these from the partition root, as we
756+
* only process one tuple at a time (therefore we won't
757+
* overwrite needed data in slots), and the results of
758+
* projections are independent of the underlying
759+
* storage. Projections and where clauses themselves don't
760+
* store state / are independent of the underlying storage.
761+
*/
762+
leaf_part_rri->ri_onConflict->oc_ProjSlot =
763+
rootResultRelInfo->ri_onConflict->oc_ProjSlot;
764+
leaf_part_rri->ri_onConflict->oc_ProjInfo =
765+
rootResultRelInfo->ri_onConflict->oc_ProjInfo;
766+
leaf_part_rri->ri_onConflict->oc_WhereClause =
767+
rootResultRelInfo->ri_onConflict->oc_WhereClause;
768+
}
740769
else
741770
{
742771
List *onconflset;
743772
TupleDesc tupDesc;
744773
bool found_whole_row;
745774

746-
leaf_part_rri->ri_onConflict = makeNode(OnConflictSetState);
747-
748775
/*
749776
* Translate expressions in onConflictSet to account for
750777
* different attribute numbers. For that, map partition
@@ -778,20 +805,17 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
778805
/* Finally, adjust this tlist to match the partition. */
779806
onconflset = adjust_partition_tlist(onconflset, map);
780807

781-
/*
782-
* Build UPDATE SET's projection info. The user of this
783-
* projection is responsible for setting the slot's tupdesc!
784-
* We set aside a tupdesc that's good for the common case of a
785-
* partition that's tupdesc-equal to the partitioned table;
786-
* partitions of different tupdescs must generate their own.
787-
*/
808+
/* create the tuple slot for the UPDATE SET projection */
788809
tupDesc = ExecTypeFromTL(onconflset);
789-
ExecSetSlotDescriptor(mtstate->mt_conflproj, tupDesc);
810+
leaf_part_rri->ri_onConflict->oc_ProjSlot =
811+
ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
812+
&TTSOpsVirtual);
813+
814+
/* build UPDATE SET projection state */
790815
leaf_part_rri->ri_onConflict->oc_ProjInfo =
791816
ExecBuildProjectionInfo(onconflset, econtext,
792-
mtstate->mt_conflproj,
817+
leaf_part_rri->ri_onConflict->oc_ProjSlot,
793818
&mtstate->ps, partrelDesc);
794-
leaf_part_rri->ri_onConflict->oc_ProjTupdesc = tupDesc;
795819

796820
/*
797821
* If there is a WHERE clause, initialize state where it will

src/backend/executor/nodeModifyTable.c

+24-46
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
13041304
ExprContext *econtext = mtstate->ps.ps_ExprContext;
13051305
Relation relation = resultRelInfo->ri_RelationDesc;
13061306
ExprState *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
1307+
TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing;
13071308
HeapTupleData tuple;
13081309
HeapUpdateFailureData hufd;
13091310
LockTupleMode lockmode;
@@ -1413,7 +1414,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
14131414
ExecCheckHeapTupleVisible(estate, &tuple, buffer);
14141415

14151416
/* Store target's existing tuple in the state's dedicated slot */
1416-
ExecStoreBufferHeapTuple(&tuple, mtstate->mt_existing, buffer);
1417+
ExecStorePinnedBufferHeapTuple(&tuple, existing, buffer);
14171418

14181419
/*
14191420
* Make tuple and any needed join variables available to ExecQual and
@@ -1422,13 +1423,13 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
14221423
* has been made to reference INNER_VAR in setrefs.c, but there is no
14231424
* other redirection.
14241425
*/
1425-
econtext->ecxt_scantuple = mtstate->mt_existing;
1426+
econtext->ecxt_scantuple = existing;
14261427
econtext->ecxt_innertuple = excludedSlot;
14271428
econtext->ecxt_outertuple = NULL;
14281429

14291430
if (!ExecQual(onConflictSetWhere, econtext))
14301431
{
1431-
ReleaseBuffer(buffer);
1432+
ExecClearTuple(existing); /* see return below */
14321433
InstrCountFiltered1(&mtstate->ps, 1);
14331434
return true; /* done with the tuple */
14341435
}
@@ -1451,7 +1452,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
14511452
* INSERT or UPDATE path.
14521453
*/
14531454
ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
1454-
mtstate->mt_existing,
1455+
existing,
14551456
mtstate->ps.state);
14561457
}
14571458

@@ -1469,11 +1470,17 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
14691470

14701471
/* Execute UPDATE with projection */
14711472
*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
1472-
mtstate->mt_conflproj, planSlot,
1473+
resultRelInfo->ri_onConflict->oc_ProjSlot,
1474+
planSlot,
14731475
&mtstate->mt_epqstate, mtstate->ps.state,
14741476
canSetTag);
14751477

1476-
ReleaseBuffer(buffer);
1478+
/*
1479+
* Clear out existing tuple, as there might not be another conflict among
1480+
* the next input rows. Don't want to hold resources till the end of the
1481+
* query.
1482+
*/
1483+
ExecClearTuple(existing);
14771484
return true;
14781485
}
14791486

@@ -1633,7 +1640,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
16331640
ResultRelInfo *targetRelInfo,
16341641
TupleTableSlot *slot)
16351642
{
1636-
ModifyTable *node;
16371643
ResultRelInfo *partrel;
16381644
PartitionRoutingInfo *partrouteinfo;
16391645
TupleConversionMap *map;
@@ -1698,19 +1704,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
16981704
slot = execute_attr_map_slot(map->attrMap, slot, new_slot);
16991705
}
17001706

1701-
/* Initialize information needed to handle ON CONFLICT DO UPDATE. */
1702-
Assert(mtstate != NULL);
1703-
node = (ModifyTable *) mtstate->ps.plan;
1704-
if (node->onConflictAction == ONCONFLICT_UPDATE)
1705-
{
1706-
Assert(mtstate->mt_existing != NULL);
1707-
ExecSetSlotDescriptor(mtstate->mt_existing,
1708-
RelationGetDescr(partrel->ri_RelationDesc));
1709-
Assert(mtstate->mt_conflproj != NULL);
1710-
ExecSetSlotDescriptor(mtstate->mt_conflproj,
1711-
partrel->ri_onConflict->oc_ProjTupdesc);
1712-
}
1713-
17141707
return slot;
17151708
}
17161709

@@ -2319,43 +2312,28 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
23192312
econtext = mtstate->ps.ps_ExprContext;
23202313
relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
23212314

2322-
/*
2323-
* Initialize slot for the existing tuple. If we'll be performing
2324-
* tuple routing, the tuple descriptor to use for this will be
2325-
* determined based on which relation the update is actually applied
2326-
* to, so we don't set its tuple descriptor here.
2327-
*/
2328-
mtstate->mt_existing =
2329-
ExecInitExtraTupleSlot(mtstate->ps.state,
2330-
mtstate->mt_partition_tuple_routing ?
2331-
NULL : relationDesc, &TTSOpsBufferHeapTuple);
2332-
23332315
/* carried forward solely for the benefit of explain */
23342316
mtstate->mt_excludedtlist = node->exclRelTlist;
23352317

23362318
/* create state for DO UPDATE SET operation */
23372319
resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
23382320

2339-
/*
2340-
* Create the tuple slot for the UPDATE SET projection.
2341-
*
2342-
* Just like mt_existing above, we leave it without a tuple descriptor
2343-
* in the case of partitioning tuple routing, so that it can be
2344-
* changed by ExecPrepareTupleRouting. In that case, we still save
2345-
* the tupdesc in the parent's state: it can be reused by partitions
2346-
* with an identical descriptor to the parent.
2347-
*/
2321+
/* initialize slot for the existing tuple */
2322+
resultRelInfo->ri_onConflict->oc_Existing =
2323+
ExecInitExtraTupleSlot(mtstate->ps.state, relationDesc,
2324+
&TTSOpsBufferHeapTuple);
2325+
2326+
/* create the tuple slot for the UPDATE SET projection */
23482327
tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
2349-
mtstate->mt_conflproj =
2350-
ExecInitExtraTupleSlot(mtstate->ps.state,
2351-
mtstate->mt_partition_tuple_routing ?
2352-
NULL : tupDesc, &TTSOpsHeapTuple);
2353-
resultRelInfo->ri_onConflict->oc_ProjTupdesc = tupDesc;
2328+
resultRelInfo->ri_onConflict->oc_ProjSlot =
2329+
ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
2330+
&TTSOpsVirtual);
23542331

23552332
/* build UPDATE SET projection state */
23562333
resultRelInfo->ri_onConflict->oc_ProjInfo =
23572334
ExecBuildProjectionInfo(node->onConflictSet, econtext,
2358-
mtstate->mt_conflproj, &mtstate->ps,
2335+
resultRelInfo->ri_onConflict->oc_ProjSlot,
2336+
&mtstate->ps,
23592337
relationDesc);
23602338

23612339
/* initialize state to evaluate the WHERE clause, if any */

src/include/nodes/execnodes.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,9 @@ typedef struct OnConflictSetState
377377
{
378378
NodeTag type;
379379

380+
TupleTableSlot *oc_Existing; /* slot to store existing target tuple in */
381+
TupleTableSlot *oc_ProjSlot; /* CONFLICT ... SET ... projection target */
380382
ProjectionInfo *oc_ProjInfo; /* for ON CONFLICT DO UPDATE SET */
381-
TupleDesc oc_ProjTupdesc; /* TupleDesc for the above projection */
382383
ExprState *oc_WhereClause; /* state for the WHERE clause */
383384
} OnConflictSetState;
384385

@@ -1109,9 +1110,7 @@ typedef struct ModifyTableState
11091110
List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */
11101111
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
11111112
bool fireBSTriggers; /* do we need to fire stmt triggers? */
1112-
TupleTableSlot *mt_existing; /* slot to store existing target tuple in */
11131113
List *mt_excludedtlist; /* the excluded pseudo relation's tlist */
1114-
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection target */
11151114

11161115
/*
11171116
* Slot for storing tuples in the root partitioned table's rowtype during

0 commit comments

Comments
 (0)