Final Thoughts for 8.3 on LWLocking and Scalability

Started by Simon Riggsover 18 years ago10 messages
#1Simon Riggs
simon@2ndquadrant.com
1 attachment(s)

I've completed a review of all of the LWlocking in the backends. This is
documented in the enclosed file. I would propose that we use this as
comments in lwlock.h or in the README, if people agree.

A number of points emerge from that analysis:

1. The ProcArrayLock is acquired Exclusive-ly by only one remaining
operation: XidCacheRemoveRunningXids(). Reducing things to that level is
brilliant work, Florian and Tom. After analysis, I am still concerned
because subxact abort could now be starved out by large number of shared
holders, then when it is acquired we may experience starvation of shared
requestors, as described in point (4) here:
http://archives.postgresql.org/pgsql-hackers/2007-07/msg00948.php
I no longer want to solve it in the way described there, but have a
solution described in a separate post on -hackers. The original solution
still seems valid, but if we can solve it another way we should.

2. CountActiveBackends() searches the whole of the proc array, even
though it could stop when it gets to commit_siblings. Stopping once the
heuristic has been determined seems like the best thing to do. A small
patch to implement this is attached.

3. ReceiveSharedInvalidMessages() takes a Shared lock on SInvalLock,
then takes an Exclusive lock later in the same routine to perform
SIDelExpiredDataEntries(). The latter routine examines data that it
hasn't touched to see if it can delete anything. If it finds anything
other than its own consumed message it will only be because it beat
another backend in the race to delete a message it just consumed. So
most callers of SIDelExpiredDataEntries() will do nothing at all, after
having queued for an X lock. I can't see the sense in that, but maybe
there is some deeper purpose? ISTM that we should only attempt to clean
the queue when it fills, during SIInsertDataEntry(), which it already
does. We want to avoid continually re-triggering postmaster signals, but
we should do that anyway with a "yes-I-already-did-that" flag, rather
than by eager cleaning of the queue, which just defers a postmaster
signal storm, but does not prevent it.

4. WALWriteLock is acquired in Shared mode by bgwriter when it runs
GetLastSegSwitchTime(). All other callers are Exclusive lockers, so the
Shared request will queue like everybody else. WALWriteLock queue length
can be long, so the bgwriter can get stuck for much longer than
bgwriter_delay when it makes this call; this happens only when
archive_timeout > 0 so probably has never shown up in any performance
testing. XLogWrite takes info_lck also, so we can move the
lastSegSwitchTime behind that lock instead. That way bgwriter need never
wait on I/O, just spin for access to info_lck. Minor change.

5. ReadNewTransactionId() is only called now by GetNextXidAndEpoch(),
but I can't find a caller of that anywhere in core or contrib. Can those
now be removed?

6. David Strong talked about doing some testing to see if
NUM_BUFFER_PARTITIONS should be increased above 16. We don't have any
further information on that. Should we increase the value to 32 or 64? A
minor increase seems safe and should provide the most gain without
decreasing performance for lower numbers of CPUs.

7. VACUUM has many contention points within it, so HOT should avoid the
annoyance of having to run VACUUM repeatedly on very small
heavily-updated tables.

I haven't further analysed the SLRU locks, since nothing much has
changed there recently and they were already pretty efficient, IIRC.

I'm working on patches for 1-4. We've moved far in recent weeks, so it
seems like we should finish the job.

Comments?

--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com

Attachments:

lwlock.h.annotatedtext/plain; charset=UTF-8; name=lwlock.h.annotatedDownload
#2Marko Kreen
markokr@gmail.com
In reply to: Simon Riggs (#1)
Re: Final Thoughts for 8.3 on LWLocking and Scalability

On 9/11/07, Simon Riggs <simon@2ndquadrant.com> wrote:

5. ReadNewTransactionId() is only called now by GetNextXidAndEpoch(),
but I can't find a caller of that anywhere in core or contrib. Can those
now be removed?

GetNextXidAndEpoch() is needed for external modules to use
8-byte transaction ids user-level. Used by txid module in
Skytools/pgq.

Please keep it.

--
marko

#3Simon Riggs
simon@2ndquadrant.com
In reply to: Marko Kreen (#2)
Re: Final Thoughts for 8.3 on LWLocking and Scalability

On Tue, 2007-09-11 at 13:31 +0300, Marko Kreen wrote:

On 9/11/07, Simon Riggs <simon@2ndquadrant.com> wrote:

5. ReadNewTransactionId() is only called now by GetNextXidAndEpoch(),
but I can't find a caller of that anywhere in core or contrib. Can those
now be removed?

GetNextXidAndEpoch() is needed for external modules to use
8-byte transaction ids user-level. Used by txid module in
Skytools/pgq.

Please keep it.

Guessed it might be you folks that needed it. I'll comment that, so we
don't forget it again.

--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com

#4Simon Riggs
simon@2ndquadrant.com
In reply to: Simon Riggs (#1)
1 attachment(s)
Re: [HACKERS] Final Thoughts for 8.3 on LWLocking and Scalability

On Tue, 2007-09-11 at 10:42 +0100, Simon Riggs wrote:

2. CountActiveBackends() searches the whole of the proc array, even
though it could stop when it gets to commit_siblings. Stopping once the
heuristic has been determined seems like the best thing to do. A small
patch to implement this is attached.

backend/access/transam/xact.c | 2 !!
backend/storage/ipc/procarray.c | 11 !!!!!!!!!!!
include/storage/procarray.h | 2 !!
3 files changed, 15 modifications(!)

--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com

Attachments:

CountActiveBackends.v1.patchtext/x-patch; charset=utf-8; name=CountActiveBackends.v1.patchDownload
Index: src/backend/access/transam/xact.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/access/transam/xact.c,v
retrieving revision 1.250
diff -c -r1.250 xact.c
*** src/backend/access/transam/xact.c	8 Sep 2007 20:31:14 -0000	1.250
--- src/backend/access/transam/xact.c	11 Sep 2007 13:11:33 -0000
***************
*** 886,892 ****
  		 * active transactions.
  		 */
  		if (CommitDelay > 0 && enableFsync &&
! 			CountActiveBackends() >= CommitSiblings)
  			pg_usleep(CommitDelay);
  
  		XLogFlush(XactLastRecEnd);
--- 886,892 ----
  		 * active transactions.
  		 */
  		if (CommitDelay > 0 && enableFsync &&
! 			NumActiveBackendsIsAtLeast(CommitSiblings))
  			pg_usleep(CommitDelay);
  
  		XLogFlush(XactLastRecEnd);
Index: src/backend/storage/ipc/procarray.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/storage/ipc/procarray.c,v
retrieving revision 1.33
diff -c -r1.33 procarray.c
*** src/backend/storage/ipc/procarray.c	8 Sep 2007 20:31:15 -0000	1.33
--- src/backend/storage/ipc/procarray.c	11 Sep 2007 13:11:35 -0000
***************
*** 1016,1030 ****
  
  
  /*
!  * CountActiveBackends --- count backends (other than myself) that are in
   *		active transactions.  This is used as a heuristic to decide if
   *		a pre-XLOG-flush delay is worthwhile during commit.
   *
   * Do not count backends that are blocked waiting for locks, since they are
   * not going to get to run until someone else commits.
   */
! int
! CountActiveBackends(void)
  {
  	ProcArrayStruct *arrayP = procArray;
  	int			count = 0;
--- 1016,1030 ----
  
  
  /*
!  * NumActiveBackendsIsAtLeast --- count backends (other than myself) that are in
   *		active transactions.  This is used as a heuristic to decide if
   *		a pre-XLOG-flush delay is worthwhile during commit.
   *
   * Do not count backends that are blocked waiting for locks, since they are
   * not going to get to run until someone else commits.
   */
! bool
! NumActiveBackendsIsAtLeast(int MinBackends)
  {
  	ProcArrayStruct *arrayP = procArray;
  	int			count = 0;
***************
*** 1047,1056 ****
  			continue;			/* do not count if no XID assigned */
  		if (proc->waitLock != NULL)
  			continue;			/* do not count if blocked on a lock */
! 		count++;
  	}
  
! 	return count;
  }
  
  /*
--- 1047,1057 ----
  			continue;			/* do not count if no XID assigned */
  		if (proc->waitLock != NULL)
  			continue;			/* do not count if blocked on a lock */
! 		if (++count >= MinBackends)
! 			return true;
  	}
  
! 	return false;
  }
  
  /*
Index: src/include/storage/procarray.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/storage/procarray.h,v
retrieving revision 1.17
diff -c -r1.17 procarray.h
*** src/include/storage/procarray.h	8 Sep 2007 20:31:15 -0000	1.17
--- src/include/storage/procarray.h	11 Sep 2007 13:11:38 -0000
***************
*** 38,44 ****
  
  extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
  												   bool allDbs);
! extern int	CountActiveBackends(void);
  extern int	CountDBBackends(Oid databaseid);
  extern int	CountUserBackends(Oid roleid);
  extern bool CheckOtherDBBackends(Oid databaseId);
--- 38,44 ----
  
  extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
  												   bool allDbs);
! extern bool	NumActiveBackendsIsAtLeast(int MinBackends);
  extern int	CountDBBackends(Oid databaseid);
  extern int	CountUserBackends(Oid roleid);
  extern bool CheckOtherDBBackends(Oid databaseId);
#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Simon Riggs (#1)
Re: Final Thoughts for 8.3 on LWLocking and Scalability

Simon Riggs <simon@2ndquadrant.com> writes:

1. The ProcArrayLock is acquired Exclusive-ly by only one remaining
operation: XidCacheRemoveRunningXids(). Reducing things to that level is
brilliant work, Florian and Tom.

It would be brilliant if it were true, but it isn't. Better look again.

regards, tom lane

#6Simon Riggs
simon@2ndquadrant.com
In reply to: Tom Lane (#5)
Re: Final Thoughts for 8.3 on LWLocking and Scalability

On Tue, 2007-09-11 at 10:21 -0400, Tom Lane wrote:

Simon Riggs <simon@2ndquadrant.com> writes:

1. The ProcArrayLock is acquired Exclusive-ly by only one remaining
operation: XidCacheRemoveRunningXids(). Reducing things to that level is
brilliant work, Florian and Tom.

It would be brilliant if it were true, but it isn't. Better look again.

On the more detailed explanation, I say "in normal operation".

My analytical notes attached to the original post show ProcArrayLock is
acquired exclusively during backend start, exit and while making a
prepared (twophase) commit. So yes, it is locked Exclusively in other
places, but they happen rarely and they actually add/remove procs from
the array, so its unlikely anything can change there anyhow.

--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com

#7Florian G. Pflug
fgp@phlo.org
In reply to: Simon Riggs (#6)
Re: Final Thoughts for 8.3 on LWLocking and Scalability

Simon Riggs wrote:

On Tue, 2007-09-11 at 10:21 -0400, Tom Lane wrote:

Simon Riggs <simon@2ndquadrant.com> writes:

1. The ProcArrayLock is acquired Exclusive-ly by only one
remaining operation: XidCacheRemoveRunningXids(). Reducing things
to that level is brilliant work, Florian and Tom.

It would be brilliant if it were true, but it isn't. Better look
again.

On the more detailed explanation, I say "in normal operation".

My analytical notes attached to the original post show ProcArrayLock
is acquired exclusively during backend start, exit and while making a
prepared (twophase) commit. So yes, it is locked Exclusively in
other places, but they happen rarely and they actually add/remove
procs from the array, so its unlikely anything can change there
anyhow.

Well, and during normal during COMMIT and ABORT, which might happen
rather frequently ;-)

I do agree, however, that XidCacheRemoveRunningXids() is the only site
left where getting rid of it might be possible, and might bring
measurable benefit for some workloads. With more effort, we might not
even need it during ABORT, but I doubt that the effort would be worth
it. While some (plpgsql intensive) workloads might abort subxacts rather
frequently, I doubt that same holds true for toplevel aborts.

I'm actually working on a patch to remove that lock from
XidCacheRemoveRunningXids(), but I'm not yet completely sure that my
approach is safe. Tom had some objections that I take rather seriously.
We'll see ;-)

greetings, Florian Pflug

#8Simon Riggs
simon@2ndquadrant.com
In reply to: Florian G. Pflug (#7)
Re: Final Thoughts for 8.3 on LWLocking and Scalability

On Tue, 2007-09-11 at 19:32 +0200, Florian G. Pflug wrote:

Simon Riggs wrote:

On Tue, 2007-09-11 at 10:21 -0400, Tom Lane wrote:

Simon Riggs <simon@2ndquadrant.com> writes:

1. The ProcArrayLock is acquired Exclusive-ly by only one
remaining operation: XidCacheRemoveRunningXids(). Reducing things
to that level is brilliant work, Florian and Tom.

It would be brilliant if it were true, but it isn't. Better look
again.

On the more detailed explanation, I say "in normal operation".

My analytical notes attached to the original post show ProcArrayLock
is acquired exclusively during backend start, exit and while making a
prepared (twophase) commit. So yes, it is locked Exclusively in
other places, but they happen rarely and they actually add/remove
procs from the array, so its unlikely anything can change there
anyhow.

Well, and during normal during COMMIT and ABORT, which might happen
rather frequently ;-)

Agreed, that part of my assessment was not accurate...

--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com

#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Simon Riggs (#1)
Re: Final Thoughts for 8.3 on LWLocking and Scalability

Simon Riggs <simon@2ndquadrant.com> writes:

I've completed a review of all of the LWlocking in the backends. This is
documented in the enclosed file. I would propose that we use this as
comments in lwlock.h or in the README, if people agree.

I don't think that putting this list in as documentation is a smart
idea --- it would inevitably become out-of-date, and misleading
documentation is worse than none. Parts of it are out of date already
(which kinda proves my point considering that very little new development
has gone into the tree since September). Since anyone who's concerned
about a particular lock can grep for uses of it pretty easily, I think
we should just figure on them doing that.

2. CountActiveBackends() searches the whole of the proc array, even
though it could stop when it gets to commit_siblings. Stopping once the
heuristic has been determined seems like the best thing to do. A small
patch to implement this is attached.

At the moment CountActiveBackends doesn't take the lock anymore, so
I'm thinking that changing this is not worthwhile.

[ sinval lock management needs redesign ]

Yup it does.

4. WALWriteLock is acquired in Shared mode by bgwriter when it runs
GetLastSegSwitchTime(). All other callers are Exclusive lockers, so the
Shared request will queue like everybody else. WALWriteLock queue length
can be long, so the bgwriter can get stuck for much longer than
bgwriter_delay when it makes this call; this happens only when
archive_timeout > 0 so probably has never shown up in any performance
testing. XLogWrite takes info_lck also, so we can move the
lastSegSwitchTime behind that lock instead. That way bgwriter need never
wait on I/O, just spin for access to info_lck. Minor change.

This seems like a possibly reasonable thing to do; did you ever write
a patch for it?

5. ReadNewTransactionId() is only called now by GetNextXidAndEpoch(),
but I can't find a caller of that anywhere in core or contrib. Can those
now be removed?

No. It's needed by Slony.

6. David Strong talked about doing some testing to see if
NUM_BUFFER_PARTITIONS should be increased above 16. We don't have any
further information on that. Should we increase the value to 32 or 64?

Not without some testing.

regards, tom lane

#10Simon Riggs
simon@2ndquadrant.com
In reply to: Tom Lane (#9)
1 attachment(s)
Re: Final Thoughts for 8.3 on LWLocking and Scalability

On Wed, 2008-03-19 at 12:24 -0400, Tom Lane wrote:

[ sinval lock management needs redesign ]

Yup it does.

I wrote a redesigned, simplified version of my earlier patch. Enclosed
here for discussion only, not expecting this to be the final version.
Comments at top of patch explain it.

The basic idea is to identify a single backend to delete the sinval
message queue, without the need for redesigning the postmaster to handle
single-backend invalidation messages.

4. WALWriteLock is acquired in Shared mode by bgwriter when it runs
GetLastSegSwitchTime(). All other callers are Exclusive lockers, so the
Shared request will queue like everybody else. WALWriteLock queue length
can be long, so the bgwriter can get stuck for much longer than
bgwriter_delay when it makes this call; this happens only when
archive_timeout > 0 so probably has never shown up in any performance
testing. XLogWrite takes info_lck also, so we can move the
lastSegSwitchTime behind that lock instead. That way bgwriter need never
wait on I/O, just spin for access to info_lck. Minor change.

This seems like a possibly reasonable thing to do; did you ever write
a patch for it?

No, but happy to do so.

--
Simon Riggs
2ndQuadrant http://www.2ndQuadrant.com

PostgreSQL UK 2008 Conference: http://www.postgresql.org.uk

Attachments:

sinval_contention.v3.patchtext/x-patch; charset=utf-8; name=sinval_contention.v3.patchDownload
Index: src/backend/storage/ipc/sinval.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/storage/ipc/sinval.c,v
retrieving revision 1.85
diff -c -r1.85 sinval.c
*** src/backend/storage/ipc/sinval.c	17 Mar 2008 11:50:26 -0000	1.85
--- src/backend/storage/ipc/sinval.c	19 Mar 2008 17:51:19 -0000
***************
*** 69,74 ****
--- 69,86 ----
   * Hence, we must be holding no SI resources when we call them.  The only
   * bad side-effect is that SIDelExpiredDataEntries might be called extra
   * times on the way out of a nested call.
+  *
+  * We minimise the number of times SIDelExpiredDataEntries is called by having
+  * just one backend set as the appointed deleter. Initially this is not set
+  * until the queue reaches 50% full. Once set the appointed backend will
+  * perform the delete and then pass the role onto another backend. If the
+  * appointed backend should go away then new inserted messages will reappoint
+  * another backend when the queue gets long enough. This works well if there
+  * is just one backend with the longest queue length. If more than one backend
+  * shares the burden of having the longest queue length then we may experience
+  * some inefficiency. This is unlikely with long queue lengths, but could be
+  * fairly common with short queues. So we unset the appointed_deleter if the
+  * queue length drops below 25% of the maximum queue length. 
   */
  void
  ReceiveSharedInvalidMessages(
***************
*** 77,83 ****
  {
  	SharedInvalidationMessage data;
  	int			getResult;
! 	bool		gotMessage = false;
  
  	for (;;)
  	{
--- 89,95 ----
  {
  	SharedInvalidationMessage data;
  	int			getResult;
! 	bool			appointed_deleter = false;
  
  	for (;;)
  	{
***************
*** 87,93 ****
  		 */
  		catchupInterruptOccurred = 0;
  
! 		getResult = SIGetDataEntry(MyBackendId, &data);
  
  		if (getResult == 0)
  			break;				/* nothing more to do */
--- 99,105 ----
  		 */
  		catchupInterruptOccurred = 0;
  
! 		getResult = SIGetDataEntry(MyBackendId, &data, &appointed_deleter);
  
  		if (getResult == 0)
  			break;				/* nothing more to do */
***************
*** 102,112 ****
  			/* got a normal data message */
  			invalFunction(&data);
  		}
- 		gotMessage = true;
  	}
  
! 	/* If we got any messages, try to release dead messages */
! 	if (gotMessage)
  		SIDelExpiredDataEntries(false);
  }
  
--- 114,123 ----
  			/* got a normal data message */
  			invalFunction(&data);
  		}
  	}
  
! 	/* If we are The One, try to release dead messages */
! 	if (appointed_deleter)
  		SIDelExpiredDataEntries(false);
  }
  
Index: src/backend/storage/ipc/sinvaladt.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/storage/ipc/sinvaladt.c,v
retrieving revision 1.68
diff -c -r1.68 sinvaladt.c
*** src/backend/storage/ipc/sinvaladt.c	17 Mar 2008 11:50:27 -0000	1.68
--- src/backend/storage/ipc/sinvaladt.c	19 Mar 2008 18:11:35 -0000
***************
*** 83,88 ****
--- 83,92 ----
  	int			lastBackend;	/* index of last active procState entry, +1 */
  	int			maxBackends;	/* size of procState array */
  	int			freeBackends;	/* number of empty procState slots */
+ 	int			appointed_deleter;      /* backend has been appointed as
+ 							 * the one to perform deletion of the
+ 							 * message queue
+ 							 */
  
  	/*
  	 * Next LocalTransactionId to use for each idle backend slot.  We keep
***************
*** 159,164 ****
--- 163,170 ----
  	shmInvalBuffer->maxBackends = MaxBackends;
  	shmInvalBuffer->freeBackends = MaxBackends;
  
+ 	shmInvalBuffer->appointed_deleter = -1;
+ 
  	/* The buffer[] array is initially all unused, so we need not fill it */
  
  	/* Mark all backends inactive, and initialize nextLXID */
***************
*** 321,326 ****
--- 327,340 ----
  	}
  
  	/*
+ 	 * Try to prevent table overflow.  When the table is >50% full 
+ 	 * issue a delete every ~6% of messages insertions. We do this
+ 	 * to avoid hitting the next higher limit where we wake everybody.
+ 	 */
+ 	if ((numMsgs > (MAXNUMMESSAGES / 2)) && (segP->maxMsgNum % (MAXNUMMESSAGES / 16) == 0))
+ 		SIDelExpiredDataEntries(true);
+ 
+ 	/*
  	 * Try to prevent table overflow.  When the table is 70% full send a
  	 * WAKEN_CHILDREN request to the postmaster.  The postmaster will send a
  	 * SIGUSR1 signal to all the backends, which will cause sinval.c to read
***************
*** 333,338 ****
--- 347,354 ----
  	if (numMsgs == (MAXNUMMESSAGES * 70 / 100) &&
  		IsUnderPostmaster)
  	{
+ 		SIDelExpiredDataEntries(true);
+ 
  		elog(DEBUG4, "SI table is 70%% full, signaling postmaster");
  		signal_postmaster = true;
  	}
***************
*** 365,370 ****
--- 381,388 ----
  	segP->minMsgNum = 0;
  	segP->maxMsgNum = 0;
  
+ 	segP->appointed_deleter = -1;
+ 
  	for (i = 0; i < segP->lastBackend; i++)
  	{
  		if (segP->procState[i].nextMsgNum >= 0) /* active backend? */
***************
*** 394,400 ****
   * allowing SInvalLock to be grabbed in shared mode for any other reason!
   */
  int
! SIGetDataEntry(int backendId, SharedInvalidationMessage *data)
  {
  	ProcState  *stateP;
  	SISeg	   *segP;
--- 412,418 ----
   * allowing SInvalLock to be grabbed in shared mode for any other reason!
   */
  int
! SIGetDataEntry(int backendId, SharedInvalidationMessage *data, bool *appointed_deleter)
  {
  	ProcState  *stateP;
  	SISeg	   *segP;
***************
*** 433,438 ****
--- 451,459 ----
  	 * delete it here. SIDelExpiredDataEntries() should be called to remove
  	 * dead messages.
  	 */
+ 	if (segP->appointed_deleter == MyBackendId)
+ 		*appointed_deleter = true;
+ 
  
  	LWLockRelease(SInvalLock);
  	return 1;					/* got a message */
***************
*** 449,454 ****
--- 470,476 ----
  	int			min,
  				i,
  				h;
+ 	int			deleter = -1;
  
  	if (!locked)
  		LWLockAcquire(SInvalLock, LW_EXCLUSIVE);
***************
*** 469,479 ****
--- 491,517 ----
  		if (h >= 0)
  		{						/* backend active */
  			if (h < min)
+ 			{
  				min = h;
+ 				deleter = i + 1;
+ 			}
  		}
  	}
  	segP->minMsgNum = min;
  
+ 	/* 
+ 	 * Set the appointed deleter to be the highest BackendId that shares
+ 	 * the minimum message number. This way we have only one appointed
+ 	 * deleter, so we can minimise calls to this function, since it
+ 	 * requires us to hold an Exclusive lock. Only assign a deleter if
+ 	 * the message queue is still long, so we can avoid nibbling away
+ 	 * at the queue.
+ 	 */
+ 	if ((segP->maxMsgNum - segP->minMsgNum) > (MAXNUMMESSAGES / 4)) 
+ 		segP->appointed_deleter = deleter;
+ 	else
+ 		segP->appointed_deleter = -1;
+ 
  	/*
  	 * When minMsgNum gets really large, decrement all message counters so as
  	 * to forestall overflow of the counters.
Index: src/include/storage/sinvaladt.h
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/include/storage/sinvaladt.h,v
retrieving revision 1.47
diff -c -r1.47 sinvaladt.h
*** src/include/storage/sinvaladt.h	17 Mar 2008 11:50:27 -0000	1.47
--- src/include/storage/sinvaladt.h	19 Mar 2008 17:34:06 -0000
***************
*** 32,38 ****
  extern void SharedInvalBackendInit(void);
  
  extern bool SIInsertDataEntry(SharedInvalidationMessage *data);
! extern int SIGetDataEntry(int backendId, SharedInvalidationMessage *data);
  extern void SIDelExpiredDataEntries(bool locked);
  
  extern LocalTransactionId GetNextLocalTransactionId(void);
--- 32,39 ----
  extern void SharedInvalBackendInit(void);
  
  extern bool SIInsertDataEntry(SharedInvalidationMessage *data);
! extern int SIGetDataEntry(int backendId, SharedInvalidationMessage *data
! 						, bool *appointed_deleter);
  extern void SIDelExpiredDataEntries(bool locked);
  
  extern LocalTransactionId GetNextLocalTransactionId(void);