Display the time when the process started waiting for the lock, in pg_locks, take 2
authorFujii Masao <fujii@postgresql.org>
Mon, 15 Feb 2021 06:13:37 +0000 (15:13 +0900)
committerFujii Masao <fujii@postgresql.org>
Mon, 15 Feb 2021 06:13:37 +0000 (15:13 +0900)
This commit adds new column "waitstart" into pg_locks view. This column
reports the time when the server process started waiting for the lock
if the lock is not held. This information is useful, for example, when
examining the amount of time to wait on a lock by subtracting
"waitstart" in pg_locks from the current time, and identify the lock
that the processes are waiting for very long.

This feature uses the current time obtained for the deadlock timeout
timer as "waitstart" (i.e., the time when this process started waiting
for the lock). Since getting the current time newly can cause overhead,
we reuse the already-obtained time to avoid that overhead.

Note that "waitstart" is updated without holding the lock table's
partition lock, to avoid the overhead by additional lock acquisition.
This can cause "waitstart" in pg_locks to become NULL for a very short
period of time after the wait started even though "granted" is false.
This is OK in practice because we can assume that users are likely to
look at "waitstart" when waiting for the lock for a long time.

The first attempt of this patch (commit 3b733fcd04) caused the buildfarm
member "rorqual" (built with --disable-atomics --disable-spinlocks) to report
the failure of the regression test. It was reverted by commit 890d2182a2.
The cause of this failure was that the atomic variable for "waitstart"
in the dummy process entry created at the end of prepare transaction was
not initialized. This second attempt fixes that issue.

Bump catalog version.

Author: Atsushi Torikoshi
Reviewed-by: Ian Lawrence Barwick, Robert Haas, Justin Pryzby, Fujii Masao
Discussion: https://postgr.es/m/a96013dc51cdc56b2a2b84fa8a16a993@oss.nttdata.com

12 files changed:
contrib/amcheck/expected/check_btree.out
doc/src/sgml/catalogs.sgml
src/backend/access/transam/twophase.c
src/backend/storage/ipc/standby.c
src/backend/storage/lmgr/lock.c
src/backend/storage/lmgr/proc.c
src/backend/utils/adt/lockfuncs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/storage/lock.h
src/include/storage/proc.h
src/test/regress/expected/rules.out

index 13848b7449b7ff487493bff181f920c2eb52c404..5a3f1ef737cf89de4a33ca82c14626edd4c711dc 100644 (file)
@@ -97,8 +97,8 @@ SELECT bt_index_parent_check('bttest_b_idx');
 SELECT * FROM pg_locks
 WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx']::regclass[])
     AND pid = pg_backend_pid();
- locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted | fastpath 
-----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----+------+---------+----------
+ locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted | fastpath | waitstart 
+----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----+------+---------+----------+-----------
 (0 rows)
 
 COMMIT;
index 692ad65de2d77cf807792df8111b6c77f373ea68..db29905e91ffa87d7bf9b02c8e4976dc18d0f9cb 100644 (file)
@@ -10605,6 +10605,19 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        lock table
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>waitstart</structfield> <type>timestamptz</type>
+      </para>
+      <para>
+       Time when the server process started waiting for this lock,
+       or null if the lock is held.
+       Note that this can be null for a very short period of time after
+       the wait started even though <structfield>granted</structfield>
+       is <literal>false</literal>.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
index fc18b778324ddf78086db63c9a6782e7ef5ccd23..70d22577ceee755b7fcf9a4ed5c60e02f85970d3 100644 (file)
@@ -873,8 +873,15 @@ PGPROC *
 TwoPhaseGetDummyProc(TransactionId xid, bool lock_held)
 {
    GlobalTransaction gxact = TwoPhaseGetGXact(xid, lock_held);
+   PGPROC     *dummy = &ProcGlobal->allProcs[gxact->pgprocno];
 
-   return &ProcGlobal->allProcs[gxact->pgprocno];
+   /*
+    * Initialize atomic variable in dummy proc so that GetLockStatusData()
+    * can read it later.
+    */
+   pg_atomic_init_u64(&dummy->waitStart, 0);
+
+   return dummy;
 }
 
 /************************************************************************/
index 39a30c00f7a3670154c32b9b2f7f455c85a5a841..5877a60715f742aeb4624ee6b89cd065ccb3d563 100644 (file)
@@ -540,12 +540,34 @@ void
 ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 {
    TimestampTz ltime;
+   TimestampTz now;
 
    Assert(InHotStandby);
 
    ltime = GetStandbyLimitTime();
+   now = GetCurrentTimestamp();
 
-   if (GetCurrentTimestamp() >= ltime && ltime != 0)
+   /*
+    * Update waitStart if first time through after the startup process
+    * started waiting for the lock. It should not be updated every time
+    * ResolveRecoveryConflictWithLock() is called during the wait.
+    *
+    * Use the current time obtained for comparison with ltime as waitStart
+    * (i.e., the time when this process started waiting for the lock). Since
+    * getting the current time newly can cause overhead, we reuse the
+    * already-obtained time to avoid that overhead.
+    *
+    * Note that waitStart is updated without holding the lock table's
+    * partition lock, to avoid the overhead by additional lock acquisition.
+    * This can cause "waitstart" in pg_locks to become NULL for a very short
+    * period of time after the wait started even though "granted" is false.
+    * This is OK in practice because we can assume that users are likely to
+    * look at "waitstart" when waiting for the lock for a long time.
+    */
+   if (pg_atomic_read_u64(&MyProc->waitStart) == 0)
+       pg_atomic_write_u64(&MyProc->waitStart, now);
+
+   if (now >= ltime && ltime != 0)
    {
        /*
         * We're already behind, so clear a path as quickly as possible.
index 79c1cf9b8b4e39df83a10ff766d5e0a14de69c94..108b4d9023898a01a23a475850b5c43b18e75caa 100644 (file)
@@ -3619,6 +3619,12 @@ GetLockStatusData(void)
            instance->leaderPid = proc->pid;
            instance->fastpath = true;
 
+           /*
+            * Successfully taking fast path lock means there were no
+            * conflicting locks.
+            */
+           instance->waitStart = 0;
+
            el++;
        }
 
@@ -3646,6 +3652,7 @@ GetLockStatusData(void)
            instance->pid = proc->pid;
            instance->leaderPid = proc->pid;
            instance->fastpath = true;
+           instance->waitStart = 0;
 
            el++;
        }
@@ -3698,6 +3705,7 @@ GetLockStatusData(void)
        instance->pid = proc->pid;
        instance->leaderPid = proclock->groupLeader->pid;
        instance->fastpath = false;
+       instance->waitStart = (TimestampTz) pg_atomic_read_u64(&proc->waitStart);
 
        el++;
    }
index c87ffc654919a5e248dab25f9b4bde82b6a6d500..0884909a2206ebfe5c96a5881d99d6f6c6f7ec35 100644 (file)
@@ -402,6 +402,7 @@ InitProcess(void)
    MyProc->lwWaitMode = 0;
    MyProc->waitLock = NULL;
    MyProc->waitProcLock = NULL;
+   pg_atomic_init_u64(&MyProc->waitStart, 0);
 #ifdef USE_ASSERT_CHECKING
    {
        int         i;
@@ -580,6 +581,7 @@ InitAuxiliaryProcess(void)
    MyProc->lwWaitMode = 0;
    MyProc->waitLock = NULL;
    MyProc->waitProcLock = NULL;
+   pg_atomic_init_u64(&MyProc->waitStart, 0);
 #ifdef USE_ASSERT_CHECKING
    {
        int         i;
@@ -1262,6 +1264,23 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
        }
        else
            enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
+
+       /*
+        * Use the current time obtained for the deadlock timeout timer as
+        * waitStart (i.e., the time when this process started waiting for the
+        * lock). Since getting the current time newly can cause overhead, we
+        * reuse the already-obtained time to avoid that overhead.
+        *
+        * Note that waitStart is updated without holding the lock table's
+        * partition lock, to avoid the overhead by additional lock
+        * acquisition. This can cause "waitstart" in pg_locks to become NULL
+        * for a very short period of time after the wait started even though
+        * "granted" is false. This is OK in practice because we can assume
+        * that users are likely to look at "waitstart" when waiting for the
+        * lock for a long time.
+        */
+       pg_atomic_write_u64(&MyProc->waitStart,
+                           get_timeout_start_time(DEADLOCK_TIMEOUT));
    }
    else if (log_recovery_conflict_waits)
    {
@@ -1678,6 +1697,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
    proc->waitLock = NULL;
    proc->waitProcLock = NULL;
    proc->waitStatus = waitStatus;
+   pg_atomic_write_u64(&MyProc->waitStart, 0);
 
    /* And awaken it */
    SetLatch(&proc->procLatch);
index b1cf5b79a75fed6cfac1474fb2b4217b6caa1c42..97f0265c12debc7829a170b20ec1f0ac2cb1c2e9 100644 (file)
@@ -63,7 +63,7 @@ typedef struct
 } PG_Lock_Status;
 
 /* Number of columns in pg_locks output */
-#define NUM_LOCK_STATUS_COLUMNS        15
+#define NUM_LOCK_STATUS_COLUMNS        16
 
 /*
  * VXIDGetDatum - Construct a text representation of a VXID
@@ -142,6 +142,8 @@ pg_lock_status(PG_FUNCTION_ARGS)
                           BOOLOID, -1, 0);
        TupleDescInitEntry(tupdesc, (AttrNumber) 15, "fastpath",
                           BOOLOID, -1, 0);
+       TupleDescInitEntry(tupdesc, (AttrNumber) 16, "waitstart",
+                          TIMESTAMPTZOID, -1, 0);
 
        funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
@@ -336,6 +338,10 @@ pg_lock_status(PG_FUNCTION_ARGS)
        values[12] = CStringGetTextDatum(GetLockmodeName(instance->locktag.locktag_lockmethodid, mode));
        values[13] = BoolGetDatum(granted);
        values[14] = BoolGetDatum(instance->fastpath);
+       if (!granted && instance->waitStart != 0)
+           values[15] = TimestampTzGetDatum(instance->waitStart);
+       else
+           nulls[15] = true;
 
        tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
        result = HeapTupleGetDatum(tuple);
@@ -406,6 +412,7 @@ pg_lock_status(PG_FUNCTION_ARGS)
        values[12] = CStringGetTextDatum("SIReadLock");
        values[13] = BoolGetDatum(true);
        values[14] = BoolGetDatum(false);
+       nulls[15] = true;
 
        tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
        result = HeapTupleGetDatum(tuple);
index 2efd937e12e554f93e2018a9c6eeb77a8fe61706..ac8b8e7ee8a3fad427d1345928f532549e355144 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202102121
+#define CATALOG_VERSION_NO 202102151
 
 #endif
index 4e0c9be58c31474f6d506d7aedb0f37f50d17096..1487710d590773c8a64170c24f78c1370f0de834 100644 (file)
 { oid => '1371', descr => 'view system lock information',
   proname => 'pg_lock_status', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,oid,oid,int4,int2,text,xid,oid,oid,int2,text,int4,text,bool,bool}',
-  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}',
+  proallargtypes => '{text,oid,oid,int4,int2,text,xid,oid,oid,int2,text,int4,text,bool,bool,timestamptz}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath,waitstart}',
   prosrc => 'pg_lock_status' },
 { oid => '2561',
   descr => 'get array of PIDs of sessions blocking specified backend PID from acquiring a heavyweight lock',
index 68a3487d49f3c40a4e36927eb4dcb1ddbc22de50..9b2a421c32c25e8a69a84194a337d943cd2ffd57 100644 (file)
@@ -22,6 +22,7 @@
 #include "storage/lockdefs.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
+#include "utils/timestamp.h"
 
 /* struct PGPROC is declared in proc.h, but must forward-reference it */
 typedef struct PGPROC PGPROC;
@@ -446,6 +447,8 @@ typedef struct LockInstanceData
    LOCKMODE    waitLockMode;   /* lock awaited by this PGPROC, if any */
    BackendId   backend;        /* backend ID of this PGPROC */
    LocalTransactionId lxid;    /* local transaction ID of this PGPROC */
+   TimestampTz waitStart;      /* time at which this PGPROC started waiting
+                                * for lock */
    int         pid;            /* pid of this PGPROC */
    int         leaderPid;      /* pid of group leader; = pid if no group */
    bool        fastpath;       /* taken via fastpath? */
index 683ab64f76b8d7261bd9b14bbcf7412b5b780c03..a777cb64a1cfc687d06799639706cb9163e59218 100644 (file)
@@ -181,6 +181,8 @@ struct PGPROC
    LOCKMODE    waitLockMode;   /* type of lock we're waiting for */
    LOCKMASK    heldLocks;      /* bitmask for lock types already held on this
                                 * lock object by this backend */
+   pg_atomic_uint64 waitStart; /* time at which wait for lock acquisition
+                                * started */
 
    bool        delayChkpt;     /* true if this proc delays checkpoint start */
 
index b632d9f2eab18d47e632ebb9b1c1c466834ad3c8..10a1f34ebc172a25d56cb296a3e1904f554ac809 100644 (file)
@@ -1394,8 +1394,9 @@ pg_locks| SELECT l.locktype,
     l.pid,
     l.mode,
     l.granted,
-    l.fastpath
-   FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath);
+    l.fastpath,
+    l.waitstart
+   FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
     pg_get_userbyid(c.relowner) AS matviewowner,