Introduction

One of the most useful debugging tools in modern PostgreSQL is the wait event system. When a query slows down or a database becomes CPU bound, a natural question is: “What are sessions actually waiting on?” Postgres exposes this information through the pg_stat_activity view via two columns:

wait_event_type
wait_event

These fields reveal what the backend process is blocked on at a given moment. Among the different wait types, one category tends to cause confusion:

LWLock

If you’ve ever seen dashboards full of LWLock waits, you’re not alone in wondering what they mean and whether they’re a problem.


Where Wait Events Appear

The easiest way to see wait events is:

SELECT pid,
wait_event_type,
wait_event,
state,
query
FROM pg_stat_activity
WHERE state != 'idle';

Example output might look like:

pid wait_event_type wait_event state
1234 Lock transactionid active
5678 LWLock buffer_content active
9012 IO DataFileRead active

Each category represents a different kind of wait. Common types include:

  • Lock
  • LWLock
  • IO
  • Client
  • IPC
  • Activity

Among these, LWLock waits often appear during performance incidents.


What Is an LWLock?

LWLock stands for Lightweight Lock. These are internal Postgres synchronization primitives used to coordinate access to shared memory structures. Note that they are NOT related to lock contention on tables, or deadlocking when performing DML. LWLocks protect important internal structures such as:

  • shared buffers
  • WAL buffers
  • lock tables
  • SLRU caches

Because these structures are accessed by many processes simultaneously, Postgres must coordinate access carefully.


Why LWLock Waits Appear

In healthy systems, LWLocks are acquired and released very quickly. However, they can become visible when:

  • contention increases
  • many sessions access the same internal structure
  • CPU saturation occurs
  • shared memory structures become hot spots

Seeing LWLock waits in pg_stat_activity doesn’t automatically mean something is wrong. But persistent LWLock contention usually indicates a scaling issue somewhere in the workload.


Common LWLock Wait Events

A few LWLock events appear frequently during real-world incidents.

Understanding them can help narrow down the root cause.

buffer_content

wait_event_type = LWLock
wait_event = buffer_content

This occurs when Postgres processes compete to access a shared buffer page.

Typical causes include:

  • many concurrent updates to the same rows
  • heavy index modifications
  • hot tables receiving high write volume

If you see these locks, try these troubleshooting steps:

  • check for write-heavy workloads
  • inspect tables experiencing frequent updates
  • look for missing indexes causing excessive page access

WALWriteLock

wait_event = WALWriteLock

This indicates contention while writing to the Write-Ahead Log (WAL).

Common causes:

  • high write throughput
  • large batch inserts or updates
  • slow storage affecting WAL flushes

Possible diagnostic steps:

  • examine WAL generation rate
  • check disk latency
  • review bulk write workloads

In some systems this appears as commit latency spikes.

WALInsertLock

wait_event = WALInsertLock

This occurs when multiple sessions attempt to insert WAL records simultaneously. It usually appears when:

  • many concurrent transactions are committing
  • high insert/update workloads exist
  • transaction throughput is extremely high

Postgres versions over time have reduced contention here by increasing WAL insertion slots. Still, very high write concurrency can trigger it.

ProcArrayLock

wait_event = ProcArrayLock

This lock protects Postgres’ internal structure tracking active transactions. It is often associated with:

  • snapshot creation
  • visibility checks
  • large numbers of active connections

Possible causes include:

  • very high connection counts
  • long-running transactions
  • frequent snapshot creation

Connection pooling (and lowering max_connection) often helps reduce this type of contention.

CLogControlLock / SLRU Locks

wait_event = CLogControlLock

These involve the SLRU (Simple Least Recently Used) subsystem, which tracks transaction commit status. Heavy contention here can appear when:

  • extremely high transaction rates exist
  • frequent visibility checks occur
  • many short transactions are executed

Diagnosing LWLock Problems

When investigating LWLock waits, a few steps usually help.

1. Look for dominant wait events

Start by identifying which LWLock appears most frequently:

SELECT wait_event, count(*)
FROM pg_stat_activity
WHERE wait_event_type = 'LWLock'
GROUP BY wait_event
ORDER BY count(*) DESC;

2. Examine workload characteristics

Questions to ask:

  • Are there many concurrent writers?
  • Is a single table receiving heavy updates?
  • Are there extremely high transaction rates?

3. Check connection counts

Large numbers of connections can amplify contention. Connection pooling often reduces LWLock pressure significantly.

4. Look at query patterns

High-frequency queries touching the same rows or pages can create hotspots.


Final Thoughts

PostgreSQL’s wait event system provides valuable insight into what the database is doing internally. LWLocks, in particular, reveal contention inside shared memory structures that are otherwise invisible. When investigating performance issues, a good rule of thumb is: If many sessions are waiting on the same LWLock, there is usually a workload hotspot somewhere. Once you know where the contention lives, the path toward fixing it becomes much clearer.