From ea4ee0a1f317874053c303777a80636249cc36e1 Mon Sep 17 00:00:00 2001 From: "Min, Baohong" Date: Thu, 23 Jul 2026 09:40:08 +0800 Subject: [PATCH 1/1] Reduce LWLockWaitListLock() cache-line contention with adaptive spin reads Under high lock contention (100+ CPUs, 500+ clients), backends spinning in LWLockWaitListLock() re-read lock->state on every iteration, causing heavy cache-line bouncing that limits throughput. Re-read lock->state only periodically, with the interval adapting to the contention level. This optimization significantly improves performance under high contention while leaving low-contention cases (few cores and clients) unaffected. Co-authored-by: Min, Baohong Co-authored-by: Jin, Jun Co-authored-by: Kim, Andrew --- src/backend/storage/lmgr/lwlock.c | 25 ++++++++++++++++++++++++- src/backend/storage/lmgr/s_lock.c | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index b1ad396ba79..4fb61ec7fcd 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -166,6 +166,14 @@ typedef struct LWLockHandle static int num_held_lwlocks = 0; static LWLockHandle held_lwlocks[MAX_SIMUL_LWLOCKS]; +/* + * Adaptive spin delay for LWLockWaitListLock: controls how often we + * re-read the lock state while spinning. This reduces cache line contention + * when multiple backends are waiting on the same lock. + */ +#define SPINS_PER_LOCK_READ_THRESHOLD 5 +static int spins_per_lock_read = 1; + /* Maximum number of LWLock tranches that can be assigned by extensions */ #define MAX_USER_DEFINED_TRANCHES 256 @@ -857,13 +865,28 @@ LWLockWaitListLock(LWLock *lock) { SpinDelayStatus delayStatus; + int spins_since_read = 0; + int lock_read_count = 0; + init_local_spin_delay(&delayStatus); while (old_state & LW_FLAG_LOCKED) { perform_spin_delay(&delayStatus); - old_state = pg_atomic_read_u32(&lock->state); + /* Only read lock state periodically to reduce cache contention */ + if (++spins_since_read >= spins_per_lock_read) + { + old_state = pg_atomic_read_u32(&lock->state); + ++lock_read_count; + spins_since_read = 0; + } } + /* Adaptively adjust read interval based on wait duration */ + if (lock_read_count > SPINS_PER_LOCK_READ_THRESHOLD) + spins_per_lock_read = Min(spins_per_lock_read + 5, 256); + else + spins_per_lock_read = Max(spins_per_lock_read - 1, 1); + #ifdef LWLOCK_STATS delays += delayStatus.delays; #endif diff --git a/src/backend/storage/lmgr/s_lock.c b/src/backend/storage/lmgr/s_lock.c index 6df568eccb3..3bb1205c349 100644 --- a/src/backend/storage/lmgr/s_lock.c +++ b/src/backend/storage/lmgr/s_lock.c @@ -57,7 +57,7 @@ #define MIN_SPINS_PER_DELAY 10 #define MAX_SPINS_PER_DELAY 1000 #define NUM_DELAYS 1000 -#define MIN_DELAY_USEC 1000L +#define MIN_DELAY_USEC 100L #define MAX_DELAY_USEC 1000000L #ifdef S_LOCK_TEST -- 2.43.0