Skip to content

Commit b40bc9e

Browse files
committed
Avoid O(N^2) behavior with lots of deferred triggers by making
deferredTriggerInvokeEvents only scan events added since it last ran. Stephan Szabo, some corrections by Tom Lane.
1 parent 7773434 commit b40bc9e

File tree

1 file changed

+38
-11
lines changed

1 file changed

+38
-11
lines changed

src/backend/commands/trigger.c

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1994, Regents of the University of California
88
*
99
* IDENTIFICATION
10-
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.147 2003/03/31 20:47:51 momjian Exp $
10+
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.148 2003/04/20 17:03:25 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -1626,12 +1626,18 @@ static List *deftrig_trigstates;
16261626
* Because this can grow pretty large, we don't use separate List nodes,
16271627
* but instead thread the list through the dte_next fields of the member
16281628
* nodes. Saves just a few bytes per entry, but that adds up.
1629+
*
1630+
* deftrig_events_imm holds the tail pointer as of the last
1631+
* deferredTriggerInvokeEvents call; we can use this to avoid rescanning
1632+
* entries unnecessarily. It is NULL if deferredTriggerInvokeEvents
1633+
* hasn't run since the last state change.
16291634
*
16301635
* XXX Need to be able to shove this data out to a file if it grows too
16311636
* large...
16321637
* ----------
16331638
*/
16341639
static DeferredTriggerEvent deftrig_events;
1640+
static DeferredTriggerEvent deftrig_events_imm;
16351641
static DeferredTriggerEvent deftrig_event_tail;
16361642

16371643

@@ -1845,7 +1851,7 @@ static void
18451851
deferredTriggerInvokeEvents(bool immediate_only)
18461852
{
18471853
DeferredTriggerEvent event,
1848-
prev_event = NULL;
1854+
prev_event;
18491855
MemoryContext per_tuple_context;
18501856
Relation rel = NULL;
18511857
TriggerDesc *trigdesc = NULL;
@@ -1857,13 +1863,12 @@ deferredTriggerInvokeEvents(bool immediate_only)
18571863
* are going to discard the whole event queue on return anyway, so no
18581864
* need to bother with "retail" pfree's.
18591865
*
1860-
* In a scenario with many commands in a transaction and many
1861-
* deferred-to-end-of-transaction triggers, it could get annoying to
1862-
* rescan all the deferred triggers at each command end. To speed this
1863-
* up, we could remember the actual end of the queue at EndQuery and
1864-
* examine only events that are newer. On state changes we simply
1865-
* reset the saved position to the beginning of the queue and process
1866-
* all events once with the new states.
1866+
* If immediate_only is true, we need only scan from where the end of
1867+
* the queue was at the previous deferredTriggerInvokeEvents call;
1868+
* any non-deferred events before that point are already fired.
1869+
* (But if the deferral state changes, we must reset the saved position
1870+
* to the beginning of the queue, so as to process all events once with
1871+
* the new states. See DeferredTriggerSetState.)
18671872
*/
18681873

18691874
/* Make a per-tuple memory context for trigger function calls */
@@ -1874,7 +1879,22 @@ deferredTriggerInvokeEvents(bool immediate_only)
18741879
ALLOCSET_DEFAULT_INITSIZE,
18751880
ALLOCSET_DEFAULT_MAXSIZE);
18761881

1877-
event = deftrig_events;
1882+
/*
1883+
* If immediate_only is true, then the only events that could need firing
1884+
* are those since deftrig_events_imm. (But if deftrig_events_imm is
1885+
* NULL, we must scan the entire list.)
1886+
*/
1887+
if (immediate_only && deftrig_events_imm != NULL)
1888+
{
1889+
prev_event = deftrig_events_imm;
1890+
event = prev_event->dte_next;
1891+
}
1892+
else
1893+
{
1894+
prev_event = NULL;
1895+
event = deftrig_events;
1896+
}
1897+
18781898
while (event != NULL)
18791899
{
18801900
bool still_deferred_ones = false;
@@ -1993,6 +2013,9 @@ deferredTriggerInvokeEvents(bool immediate_only)
19932013
/* Update list tail pointer in case we just deleted tail event */
19942014
deftrig_event_tail = prev_event;
19952015

2016+
/* Set the immediate event pointer for next time */
2017+
deftrig_events_imm = prev_event;
2018+
19962019
/* Release working resources */
19972020
if (rel)
19982021
heap_close(rel, NoLock);
@@ -2051,6 +2074,7 @@ DeferredTriggerBeginXact(void)
20512074
deftrig_trigstates = NIL;
20522075

20532076
deftrig_events = NULL;
2077+
deftrig_events_imm = NULL;
20542078
deftrig_event_tail = NULL;
20552079
}
20562080

@@ -2280,8 +2304,11 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
22802304
* CONSTRAINTS command applies retroactively. This happens "for free"
22812305
* since we have already made the necessary modifications to the
22822306
* constraints, and deferredTriggerEndQuery() is called by
2283-
* finish_xact_command().
2307+
* finish_xact_command(). But we must reset deferredTriggerInvokeEvents'
2308+
* tail pointer to make it rescan the entire list, in case some deferred
2309+
* events are now immediately invokable.
22842310
*/
2311+
deftrig_events_imm = NULL;
22852312
}
22862313

22872314

0 commit comments

Comments
 (0)