I have applied a modified version of this patch, attached. I trimmed
down the description of log_lock_waits to be more concise, and moved the
idea of using this to tune deadlock_timeout to the deadlock_timeout
section of the manual.
---------------------------------------------------------------------------
Simon Riggs wrote:
> On Mon, 2007-02-19 at 19:38 +0000, Simon Riggs wrote:
> > On Tue, 2007-02-13 at 22:19 -0500, Bruce Momjian wrote:
> > > Simon Riggs wrote:
> > > > On Thu, 2006-10-26 at 18:45 -0400, Tom Lane wrote:
> > > > > Chris Campbell <[EMAIL PROTECTED]> writes:
> > > > > > Is there additional logging information I can turn on to get more
> > > > > > details? I guess I need to see exactly what locks both processes
> > > > > > hold, and what queries they were running when the deadlock
> > > > > > occurred?
> > > > > > Is that easily done, without turning on logging for *all*
> > > > > > statements?
> > > > >
> > > > > log_min_error_statement = error would at least get you the statements
> > > > > reporting the deadlocks, though not what they're conflicting against.
> > > >
> > > > Yeh, we need a much better locking logger for performance analysis.
> > > >
> > > > We really need to dump the whole wait-for graph for deadlocks, since
> > > > this might be more complex than just two statements involved. Deadlocks
> > > > ought to be so infrequent that we can afford the log space to do this -
> > > > plus if we did this it would likely lead to fewer deadlocks.
> > > >
> > > > For 8.3 I'd like to have a log_min_duration_lockwait (secs) parameter
> > > > that would allow you to dump the wait-for graph for any data-level locks
> > > > that wait too long, rather than just those that deadlock. Many
> > > > applications experience heavy locking because of lack of holistic
> > > > design. That will also show up the need for other utilities to act
> > > > CONCURRENTLY, if possible.
> > >
> > > Old email, but I don't see how our current output is not good enough?
> > >
> > > test=> lock a;
> > > ERROR: deadlock detected
> > > DETAIL: Process 6855 waits for AccessExclusiveLock on relation 16394 of
> > > database 16384; blocked by process 6795.
> > > Process 6795 waits for AccessExclusiveLock on relation 16396 of database
> > > 16384; blocked by process 6855.
> >
> > This detects deadlocks, but it doesn't detect lock waits.
> >
> > When I wrote that it was previous experience driving me. Recent client
> > experience has highlighted the clear need for this. We had a lock wait
> > of 50 hours because of an RI check; thats the kind of thing I'd like to
> > show up in the logs somewhere.
> >
> > Lock wait detection can be used to show up synchronisation points that
> > have been inadvertently designed into an application, so its a useful
> > tool in investigating performance issues.
> >
> > I have a patch implementing the logging as agreed with Tom, will post to
> > patches later tonight.
>
> Patch for discussion, includes doc entries at top of patch, so its
> fairly clear how it works.
>
> Output is an INFO message, to allow this to trigger
> log_min_error_statement when it generates a message, to allow us to see
> the SQL statement that is waiting. This allows it to generate a message
> prior to the statement completing, which is important because it may not
> ever complete, in some cases, so simply logging a list of pids won't
> always tell you what the SQL was that was waiting.
>
> Other approaches are possible...
>
> Comments?
>
> --
> Simon Riggs
> EnterpriseDB http://www.enterprisedb.com
>
[ Attachment, skipping... ]
--
Bruce Momjian <[EMAIL PROTECTED]> http://momjian.us
EnterpriseDB http://www.enterprisedb.com
+ If your life is a hard drive, Christ can be your backup. +
Index: doc/src/sgml/config.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/config.sgml,v
retrieving revision 1.113
diff -c -c -r1.113 config.sgml
*** doc/src/sgml/config.sgml 2 Mar 2007 23:37:22 -0000 1.113
--- doc/src/sgml/config.sgml 3 Mar 2007 18:41:13 -0000
***************
*** 2946,2951 ****
--- 2946,2966 ----
</listitem>
</varlistentry>
+ <varlistentry id="guc-log-lock-waits" xreflabel="log_lock_waits">
+ <term><varname>log_lock_waits</varname> (<type>boolean</type>)</term>
+ <indexterm>
+ <primary><varname>log_lock_waits</> configuration parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Controls whether a log message is produced when a statement waits
+ longer than <xref linkend="guc-deadlock-timeout"> to acquire a
+ lock. This is useful in determining if lock waits are causing
+ poor performance. The default is <literal>off</>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-log-temp-files" xreflabel="log_temp_files">
<term><varname>log_temp_files</varname> (<type>integer</type>)</term>
<indexterm>
***************
*** 3980,3996 ****
This is the amount of time, in milliseconds, to wait on a lock
before checking to see if there is a deadlock condition. The
check for deadlock is relatively slow, so the server doesn't run
! it every time it waits for a lock. We (optimistically?) assume
that deadlocks are not common in production applications and
just wait on the lock for a while before starting the check for a
deadlock. Increasing this value reduces the amount of time
wasted in needless deadlock checks, but slows down reporting of
real deadlock errors. The default is one second (<literal>1s</>),
which is probably about the smallest value you would want in
! practice. On a heavily loaded server you might want to raise it.
Ideally the setting should exceed your typical transaction time,
! so as to improve the odds that a lock will be released before
! the waiter decides to check for deadlock.
</para>
</listitem>
</varlistentry>
--- 3995,4012 ----
This is the amount of time, in milliseconds, to wait on a lock
before checking to see if there is a deadlock condition. The
check for deadlock is relatively slow, so the server doesn't run
! it every time it waits for a lock. We optimistically assume
that deadlocks are not common in production applications and
just wait on the lock for a while before starting the check for a
deadlock. Increasing this value reduces the amount of time
wasted in needless deadlock checks, but slows down reporting of
real deadlock errors. The default is one second (<literal>1s</>),
which is probably about the smallest value you would want in
! practice. Set <xref linkend="guc-log-lock-waits"> to log deadlock
! checks. On a heavily loaded server you might want to raise it.
Ideally the setting should exceed your typical transaction time,
! so as to improve the odds that a lock will be released before the
! waiter decides to check for deadlock.
</para>
</listitem>
</varlistentry>
Index: src/backend/storage/lmgr/deadlock.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/storage/lmgr/deadlock.c,v
retrieving revision 1.44
diff -c -c -r1.44 deadlock.c
*** src/backend/storage/lmgr/deadlock.c 5 Jan 2007 22:19:38 -0000 1.44
--- src/backend/storage/lmgr/deadlock.c 3 Mar 2007 18:41:13 -0000
***************
*** 187,199 ****
* deadlock. If resolution is impossible, return TRUE --- the caller
* is then expected to abort the given proc's transaction.
*
! * Caller must already have locked all partitions of the lock tables.
*
* On failure, deadlock details are recorded in deadlockDetails[] for
* subsequent printing by DeadLockReport(). That activity is separate
* because we don't want to do it while holding all those LWLocks.
*/
! bool
DeadLockCheck(PGPROC *proc)
{
int i,
--- 187,200 ----
* deadlock. If resolution is impossible, return TRUE --- the caller
* is then expected to abort the given proc's transaction.
*
! * Caller must already have locked all partitions of the lock tables,
! * so standard error logging/reporting code is handled by caller.
*
* On failure, deadlock details are recorded in deadlockDetails[] for
* subsequent printing by DeadLockReport(). That activity is separate
* because we don't want to do it while holding all those LWLocks.
*/
! DeadlockState
DeadLockCheck(PGPROC *proc)
{
int i,
***************
*** 204,209 ****
--- 205,215 ----
nPossibleConstraints = 0;
nWaitOrders = 0;
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
/* Search for deadlocks and possible fixes */
if (DeadLockCheckRecurse(proc))
{
***************
*** 217,223 ****
if (!FindLockCycle(proc, possibleConstraints, &nSoftEdges))
elog(FATAL, "deadlock seems to have disappeared");
! return true; /* cannot find a non-deadlocked state */
}
/* Apply any needed rearrangements of wait queues */
--- 223,229 ----
if (!FindLockCycle(proc, possibleConstraints, &nSoftEdges))
elog(FATAL, "deadlock seems to have disappeared");
! return DS_HARD_DEADLOCK; /* cannot find a non-deadlocked state */
}
/* Apply any needed rearrangements of wait queues */
***************
*** 249,255 ****
/* See if any waiters for the lock can be woken up now */
ProcLockWakeup(GetLocksMethodTable(lock), lock);
}
! return false;
}
/*
--- 255,265 ----
/* See if any waiters for the lock can be woken up now */
ProcLockWakeup(GetLocksMethodTable(lock), lock);
}
!
! if (nWaitOrders > 0)
! return DS_SOFT_DEADLOCK;
! else
! return DS_DEADLOCK_NOT_FOUND;
}
/*
***************
*** 896,902 ****
}
/*
! * Report a detected deadlock, with available details.
*/
void
DeadLockReport(void)
--- 906,912 ----
}
/*
! * Report a detected DS_HARD_DEADLOCK, with available details.
*/
void
DeadLockReport(void)
Index: src/backend/storage/lmgr/proc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v
retrieving revision 1.184
diff -c -c -r1.184 proc.c
*** src/backend/storage/lmgr/proc.c 15 Feb 2007 23:23:23 -0000 1.184
--- src/backend/storage/lmgr/proc.c 3 Mar 2007 18:41:14 -0000
***************
*** 48,53 ****
--- 48,54 ----
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
PGPROC *MyProc = NULL;
***************
*** 979,984 ****
--- 980,986 ----
CheckDeadLock(void)
{
int i;
+ DeadlockState deadlock_state = DS_DEADLOCK_NOT_FOUND;
/*
* Acquire exclusive lock on the entire shared lock data structures. Must
***************
*** 1004,1063 ****
* This is quicker than checking our semaphore's state, since no kernel
* call is needed, and it is safe because we hold the lock partition lock.
*/
! if (MyProc->links.prev == INVALID_OFFSET ||
! MyProc->links.next == INVALID_OFFSET)
! goto check_done;
!
! #ifdef LOCK_DEBUG
! if (Debug_deadlocks)
! DumpAllLocks();
! #endif
!
! if (!DeadLockCheck(MyProc))
{
! /* No deadlock, so keep waiting */
! goto check_done;
! }
!
! /*
! * Oops. We have a deadlock.
! *
! * Get this process out of wait state. (Note: we could do this more
! * efficiently by relying on lockAwaited, but use this coding to preserve
! * the flexibility to kill some other transaction than the one detecting
! * the deadlock.)
! *
! * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
! * ProcSleep will report an error after we return from the signal handler.
! */
! Assert(MyProc->waitLock != NULL);
! RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
! /*
! * Unlock my semaphore so that the interrupted ProcSleep() call can
! * finish.
! */
! PGSemaphoreUnlock(&MyProc->sem);
! /*
! * We're done here. Transaction abort caused by the error that ProcSleep
! * will raise will cause any other locks we hold to be released, thus
! * allowing other processes to wake up; we don't need to do that here.
! * NOTE: an exception is that releasing locks we hold doesn't consider the
! * possibility of waiters that were blocked behind us on the lock we just
! * failed to get, and might now be wakable because we're not in front of
! * them anymore. However, RemoveFromWaitQueue took care of waking up any
! * such processes.
! */
/*
* Release locks acquired at head of routine. Order is not critical, so
* do it back-to-front to avoid waking another CheckDeadLock instance
* before it can get all the locks.
*/
- check_done:
for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
LWLockRelease(FirstLockMgrLock + i);
}
--- 1006,1082 ----
* This is quicker than checking our semaphore's state, since no kernel
* call is needed, and it is safe because we hold the lock partition lock.
*/
! if (MyProc->links.prev != INVALID_OFFSET &&
! MyProc->links.next != INVALID_OFFSET)
! deadlock_state = DeadLockCheck(MyProc);
!
! if (deadlock_state == DS_HARD_DEADLOCK)
{
! /*
! * Oops. We have a deadlock.
! *
! * Get this process out of wait state. (Note: we could do this more
! * efficiently by relying on lockAwaited, but use this coding to preserve
! * the flexibility to kill some other transaction than the one detecting
! * the deadlock.)
! *
! * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
! * ProcSleep will report an error after we return from the signal handler.
! */
! Assert(MyProc->waitLock != NULL);
! RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
! /*
! * Unlock my semaphore so that the interrupted ProcSleep() call can
! * finish.
! */
! PGSemaphoreUnlock(&MyProc->sem);
! /*
! * We're done here. Transaction abort caused by the error that ProcSleep
! * will raise will cause any other locks we hold to be released, thus
! * allowing other processes to wake up; we don't need to do that here.
! * NOTE: an exception is that releasing locks we hold doesn't consider the
! * possibility of waiters that were blocked behind us on the lock we just
! * failed to get, and might now be wakable because we're not in front of
! * them anymore. However, RemoveFromWaitQueue took care of waking up any
! * such processes.
! */
! }
/*
* Release locks acquired at head of routine. Order is not critical, so
* do it back-to-front to avoid waking another CheckDeadLock instance
* before it can get all the locks.
*/
for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
LWLockRelease(FirstLockMgrLock + i);
+
+ /*
+ * Issue any log messages requested.
+ *
+ * Deadlock ERROR messages are issued as part of transaction abort, so
+ * these messages should not raise error states intentionally.
+ */
+ if (log_lock_waits)
+ {
+ switch (deadlock_state)
+ {
+ case DS_SOFT_DEADLOCK:
+ ereport(LOG,
+ (errmsg("deadlock avoided by rearranging lock order")));
+ break;
+ case DS_DEADLOCK_NOT_FOUND:
+ ereport(LOG,
+ (errmsg("statement waiting for lock for at least %d ms",
+ DeadlockTimeout)));
+ break;
+ case DS_HARD_DEADLOCK:
+ break; /* ERROR message handled during abort */
+ default:
+ break;
+ }
+ }
}
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v
retrieving revision 1.377
diff -c -c -r1.377 guc.c
*** src/backend/utils/misc/guc.c 23 Feb 2007 21:36:18 -0000 1.377
--- src/backend/utils/misc/guc.c 3 Mar 2007 18:41:16 -0000
***************
*** 812,817 ****
--- 812,825 ----
#endif
{
+ {"log_lock_waits", PGC_SIGHUP, LOGGING_WHAT,
+ gettext_noop("Logs long lock wait events."),
+ NULL
+ },
+ &log_lock_waits,
+ false, NULL, NULL
+ },
+ {
{"log_hostname", PGC_SIGHUP, LOGGING_WHAT,
gettext_noop("Logs the host name in the connection logs."),
gettext_noop("By default, connection logs only show the IP address "
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.210
diff -c -c -r1.210 postgresql.conf.sample
*** src/backend/utils/misc/postgresql.conf.sample 2 Mar 2007 23:37:23 -0000 1.210
--- src/backend/utils/misc/postgresql.conf.sample 3 Mar 2007 18:41:16 -0000
***************
*** 340,346 ****
# e.g. '<%u%%%d> '
#log_statement = 'none' # none, ddl, mod, all
#log_hostname = off
!
#log_temp_files = -1 # Log temporary files equal or larger
# than the specified number of kilobytes.
# -1 disables; 0 logs all temp files
--- 340,346 ----
# e.g. '<%u%%%d> '
#log_statement = 'none' # none, ddl, mod, all
#log_hostname = off
! #log_lock_waits = off # Log lock waits longer than deadlock_timeout
#log_temp_files = -1 # Log temporary files equal or larger
# than the specified number of kilobytes.
# -1 disables; 0 logs all temp files
Index: src/include/storage/lock.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/storage/lock.h,v
retrieving revision 1.103
diff -c -c -r1.103 lock.h
*** src/include/storage/lock.h 5 Jan 2007 22:19:58 -0000 1.103
--- src/include/storage/lock.h 3 Mar 2007 18:41:17 -0000
***************
*** 394,399 ****
--- 394,406 ----
LOCKACQUIRE_ALREADY_HELD /* incremented count for lock already held */
} LockAcquireResult;
+ /* Deadlock states identified by DeadlockCheck() */
+ typedef enum
+ {
+ DS_DEADLOCK_NOT_FOUND, /* no deadlock found within database server */
+ DS_SOFT_DEADLOCK, /* deadlock, but lock queues rearrangeable */
+ DS_HARD_DEADLOCK /* deadlock, no way out but ERROR */
+ } DeadlockState;
/*
* The lockmgr's shared hash tables are partitioned to reduce contention.
***************
*** 442,448 ****
extern void lock_twophase_postabort(TransactionId xid, uint16 info,
void *recdata, uint32 len);
! extern bool DeadLockCheck(PGPROC *proc);
extern void DeadLockReport(void);
extern void RememberSimpleDeadLock(PGPROC *proc1,
LOCKMODE lockmode,
--- 449,455 ----
extern void lock_twophase_postabort(TransactionId xid, uint16 info,
void *recdata, uint32 len);
! extern DeadlockState DeadLockCheck(PGPROC *proc);
extern void DeadLockReport(void);
extern void RememberSimpleDeadLock(PGPROC *proc1,
LOCKMODE lockmode,
Index: src/include/storage/proc.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/storage/proc.h,v
retrieving revision 1.94
diff -c -c -r1.94 proc.h
*** src/include/storage/proc.h 15 Feb 2007 23:23:23 -0000 1.94
--- src/include/storage/proc.h 3 Mar 2007 18:41:17 -0000
***************
*** 127,132 ****
--- 127,133 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern bool log_lock_waits;
extern volatile bool cancel_from_timeout;
---------------------------(end of broadcast)---------------------------
TIP 1: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to [EMAIL PROTECTED] so that your
message can get through to the mailing list cleanly