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

Commit 07e4d03

Browse files
committed
Improve LISTEN startup time when there are many unread notifications.
If some existing listener is far behind, incoming new listener sessions would start from that session's read pointer and then need to advance over many already-committed notification messages, which they have no interest in. This was expensive in itself and also thrashed the pg_notify SLRU buffers a lot more than necessary. We can improve matters considerably in typical scenarios, without much added cost, by starting from the furthest-ahead read pointer, not the furthest-behind one. We do have to consider only sessions in our own database when doing this, which requires an extra field in the data structure, but that's a pretty small cost. Back-patch to 9.0 where the current LISTEN/NOTIFY logic was introduced. Matt Newell, slightly adjusted by me
1 parent 3bd909b commit 07e4d03

File tree

1 file changed

+44
-5
lines changed

1 file changed

+44
-5
lines changed

src/backend/commands/async.c

+44-5
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,19 @@ typedef struct QueuePosition
202202
(x).page != (y).page ? (y) : \
203203
(x).offset < (y).offset ? (x) : (y))
204204

205+
/* choose logically larger QueuePosition */
206+
#define QUEUE_POS_MAX(x,y) \
207+
(asyncQueuePagePrecedes((x).page, (y).page) ? (y) : \
208+
(x).page != (y).page ? (x) : \
209+
(x).offset > (y).offset ? (x) : (y))
210+
205211
/*
206212
* Struct describing a listening backend's status
207213
*/
208214
typedef struct QueueBackendStatus
209215
{
210216
int32 pid; /* either a PID or InvalidPid */
217+
Oid dboid; /* backend's database OID, or InvalidOid */
211218
QueuePosition pos; /* backend has read queue up to here */
212219
} QueueBackendStatus;
213220

@@ -235,8 +242,8 @@ typedef struct QueueBackendStatus
235242
typedef struct AsyncQueueControl
236243
{
237244
QueuePosition head; /* head points to the next free location */
238-
QueuePosition tail; /* the global tail is equivalent to the pos
239-
* of the "slowest" backend */
245+
QueuePosition tail; /* the global tail is equivalent to the pos of
246+
* the "slowest" backend */
240247
TimestampTz lastQueueFillWarn; /* time of last queue-full msg */
241248
QueueBackendStatus backend[FLEXIBLE_ARRAY_MEMBER];
242249
/* backend[0] is not used; used entries are from [1] to [MaxBackends] */
@@ -247,6 +254,7 @@ static AsyncQueueControl *asyncQueueControl;
247254
#define QUEUE_HEAD (asyncQueueControl->head)
248255
#define QUEUE_TAIL (asyncQueueControl->tail)
249256
#define QUEUE_BACKEND_PID(i) (asyncQueueControl->backend[i].pid)
257+
#define QUEUE_BACKEND_DBOID(i) (asyncQueueControl->backend[i].dboid)
250258
#define QUEUE_BACKEND_POS(i) (asyncQueueControl->backend[i].pos)
251259

252260
/*
@@ -461,6 +469,7 @@ AsyncShmemInit(void)
461469
for (i = 0; i <= MaxBackends; i++)
462470
{
463471
QUEUE_BACKEND_PID(i) = InvalidPid;
472+
QUEUE_BACKEND_DBOID(i) = InvalidOid;
464473
SET_QUEUE_POS(QUEUE_BACKEND_POS(i), 0, 0);
465474
}
466475
}
@@ -907,6 +916,10 @@ AtCommit_Notify(void)
907916
static void
908917
Exec_ListenPreCommit(void)
909918
{
919+
QueuePosition head;
920+
QueuePosition max;
921+
int i;
922+
910923
/*
911924
* Nothing to do if we are already listening to something, nor if we
912925
* already ran this routine in this transaction.
@@ -934,10 +947,34 @@ Exec_ListenPreCommit(void)
934947
* over already-committed notifications. This ensures we cannot miss any
935948
* not-yet-committed notifications. We might get a few more but that
936949
* doesn't hurt.
950+
*
951+
* In some scenarios there might be a lot of committed notifications that
952+
* have not yet been pruned away (because some backend is being lazy about
953+
* reading them). To reduce our startup time, we can look at other
954+
* backends and adopt the maximum "pos" pointer of any backend that's in
955+
* our database; any notifications it's already advanced over are surely
956+
* committed and need not be re-examined by us. (We must consider only
957+
* backends connected to our DB, because others will not have bothered to
958+
* check committed-ness of notifications in our DB.) But we only bother
959+
* with that if there's more than a page worth of notifications
960+
* outstanding, otherwise scanning all the other backends isn't worth it.
961+
*
962+
* We need exclusive lock here so we can look at other backends' entries.
937963
*/
938-
LWLockAcquire(AsyncQueueLock, LW_SHARED);
939-
QUEUE_BACKEND_POS(MyBackendId) = QUEUE_TAIL;
964+
LWLockAcquire(AsyncQueueLock, LW_EXCLUSIVE);
965+
head = QUEUE_HEAD;
966+
max = QUEUE_TAIL;
967+
if (QUEUE_POS_PAGE(max) != QUEUE_POS_PAGE(head))
968+
{
969+
for (i = 1; i <= MaxBackends; i++)
970+
{
971+
if (QUEUE_BACKEND_DBOID(i) == MyDatabaseId)
972+
max = QUEUE_POS_MAX(max, QUEUE_BACKEND_POS(i));
973+
}
974+
}
975+
QUEUE_BACKEND_POS(MyBackendId) = max;
940976
QUEUE_BACKEND_PID(MyBackendId) = MyProcPid;
977+
QUEUE_BACKEND_DBOID(MyBackendId) = MyDatabaseId;
941978
LWLockRelease(AsyncQueueLock);
942979

943980
/* Now we are listed in the global array, so remember we're listening */
@@ -953,7 +990,8 @@ Exec_ListenPreCommit(void)
953990
*
954991
* This will also advance the global tail pointer if possible.
955992
*/
956-
asyncQueueReadAllNotifications();
993+
if (!QUEUE_POS_EQUAL(max, head))
994+
asyncQueueReadAllNotifications();
957995
}
958996

959997
/*
@@ -1156,6 +1194,7 @@ asyncQueueUnregister(void)
11561194
QUEUE_POS_EQUAL(QUEUE_BACKEND_POS(MyBackendId), QUEUE_TAIL);
11571195
/* ... then mark it invalid */
11581196
QUEUE_BACKEND_PID(MyBackendId) = InvalidPid;
1197+
QUEUE_BACKEND_DBOID(MyBackendId) = InvalidOid;
11591198
LWLockRelease(AsyncQueueLock);
11601199

11611200
/* mark ourselves as no longer listed in the global array */

0 commit comments

Comments
 (0)