pg-lock-diagnosis — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited pg-lock-diagnosis (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill guides investigation and resolution of lock contention issues in PostgreSQL using pgtuner-mcp tools.
Use this skill when the user:
pg_stat_activityidle in transaction sessions causing problemspgtuner://health/locks -- Quick lock health check (lightweight, targeted)pgtuner://health/connections -- Connection state overviewpgtuner://settings/connections -- Connection and timeout configurationThis skill extends the lock analysis portions of the `health_check` MCP Prompt (which checks locks at a surface level).
DATABASE_URIpg_stat_activity, pg_locks system viewsWhat is the user's symptom?
|
+--> "Queries are being blocked / timing out"
| --> Start with Step 1 (Active Queries) to find blockers
|
+--> "Deadlock errors in logs"
| --> Start with Step 2 (Wait Events) then Step 1 with include_idle
|
+--> "idle in transaction problems"
| --> Start with Step 1 (include_idle: true, min_duration_seconds: 60)
|
+--> "General lock performance concerns"
| --> Start with Step 3 (Health Check for locks) then Step 2
|
+--> "Lock waits during maintenance (VACUUM, REINDEX, etc.)"
--> Start with Step 1 then check Step 4 (Configuration)Tool: get_active_queries
Parameters:
include_idle: true
include_system: false
min_duration_seconds: 0What to look for:
| State | Duration | Severity | Action |
|---|---|---|---|
active | < 5s | Normal | No issue |
active | > 30s | Warning | Investigate the query |
active | > 5 min | Critical | Consider canceling |
idle in transaction | > 60s | Warning | Application not closing transactions |
idle in transaction | > 5 min | Critical | Blocks vacuum and other transactions |
idle in transaction (aborted) | any | Warning | Failed transaction not rolled back |
Agent reasoning for blocking chains:
The tool output includes information about blocked queries and their blockers. Build a blocking chain:
Blocker (PID: 1234, idle in transaction, started 10 min ago)
|
+--> Blocked (PID: 2345, waiting for RowExclusiveLock on orders)
|
+--> Blocked (PID: 3456, waiting for AccessShareLock on orders)The root blocker is the session that is NOT waiting on any lock itself. This is usually the target for resolution.
IF root blocker is `idle in transaction`: The application opened a transaction but never committed/rolled back.
IF root blocker is `active` with a long-running query: The query is holding locks while executing. May need optimization.
Tool: analyze_wait_events
Parameters:
active_only: falseNote: Using active_only: false to include idle-in-transaction sessions in the wait event analysis.
Lock-related wait events:
| Wait Event | Meaning | Common Cause |
|---|---|---|
Lock:relation | Waiting for table-level lock | DDL operations, VACUUM FULL, LOCK TABLE |
Lock:tuple | Waiting for row-level lock | Concurrent UPDATE/DELETE on same row |
Lock:transactionid | Waiting for another transaction to complete | Transaction holding row lock hasn't committed |
Lock:virtualxid | Waiting for virtual transaction ID | Usually brief, indicates snapshot conflict |
Lock:extend | Waiting to extend a relation | Concurrent inserts on same table, consider fillfactor |
Lock:advisory | Waiting for advisory lock | Application-level locking |
IF many `Lock:tuple` waits: Multiple transactions are competing for the same rows. Recommend:
SELECT ... FOR UPDATE SKIP LOCKED for queue-like patternsIF many `Lock:relation` waits: A DDL or maintenance operation is blocking normal queries:
VACUUM FULL takes AccessExclusiveLockALTER TABLE takes AccessExclusiveLockCREATE INDEX (without CONCURRENTLY) takes ShareLockFor a quick initial assessment, read the MCP resource first:
Read resource: pgtuner://health/locks
For a deeper check:
Tool: check_database_health
Parameters:
include_recommendations: true
verbose: trueFocus on the "locks" dimension score:
Tool: review_settings
Parameters:
category: "connections"
include_all_settings: falseKey lock-related settings:
| Setting | Default | Recommended | Purpose |
|---|---|---|---|
lock_timeout | 0 (infinite) | 5s-30s | Maximum time to wait for a lock before failing |
deadlock_timeout | 1s | 1s (usually fine) | Time before checking for deadlocks |
idle_in_transaction_session_timeout | 0 (infinite) | 60s-300s | Auto-terminate idle-in-transaction |
statement_timeout | 0 (infinite) | 30s-120s | Maximum query execution time |
max_locks_per_transaction | 64 | 64 (increase if needed) | Lock table size (rarely needs changing) |
If specific queries are repeatedly involved in lock contention:
Tool: get_slow_queries
Parameters:
limit: 20
min_calls: 5
order_by: "mean_time"Look for UPDATE/DELETE queries with high mean time -- they may be holding locks longer than necessary.
Then analyze the lock-holding query:
Tool: analyze_query
Parameters:
query: "<the lock-holding query>"
analyze: true
buffers: true
verbose: falseIF the query is slow due to missing indexes, adding an index will reduce lock hold time (because the query completes faster).
Symptoms: Multiple sessions waiting on Lock:transactionid. One session in idle in transaction state for minutes.
Root cause: Application opens transaction, does work outside the DB (API call, file processing), then comes back to commit.
Solution:
-- Set idle_in_transaction timeout
ALTER SYSTEM SET idle_in_transaction_session_timeout = '60s';
SELECT pg_reload_conf();
-- To immediately terminate the idle session (use with caution):
SELECT pg_terminate_backend(<blocker_pid>);Symptoms: deadlock detected errors in PostgreSQL logs.
Root cause: Two transactions update the same rows in different order:
Solution:
SELECT ... FOR UPDATE to acquire locks upfront in a deterministic orderSymptoms: ALTER TABLE or CREATE INDEX blocks all queries on the table.
Solution:
-- Use CONCURRENTLY for index operations
CREATE INDEX CONCURRENTLY idx_name ON table_name (column);
REINDEX INDEX CONCURRENTLY idx_name;
-- For ALTER TABLE, consider:
SET lock_timeout = '5s'; -- Fail fast if lock not acquired
ALTER TABLE ...; -- Retry during low-traffic periodSymptoms: VACUUM FULL holds AccessExclusiveLock, blocking all access.
Solution:
VACUUM instead (only takes ShareUpdateExclusiveLock, does not block reads/writes)pg_repack for online table rewriting without exclusive locksVACUUM FULL only during maintenance windowsSymptoms: One DDL statement queued behind a long-running query, then all subsequent queries queue behind the DDL.
Explanation: PostgreSQL lock queue is FIFO. If an ALTER TABLE (needs AccessExclusiveLock) waits for a long SELECT, all new queries also wait behind the ALTER.
Solution:
-- Set a lock_timeout before DDL
SET lock_timeout = '3s';
ALTER TABLE ...;
-- If it fails, retry later. Don't let the DDL queue block everyone.Current Blocking Chains:
Root Blocker: PID 1234 | State: idle in transaction | Duration: 12 min
Query: BEGIN; UPDATE orders SET ... WHERE id = 42;
|
+--> Blocked: PID 2345 | Waiting: 3 min | Lock: RowExclusiveLock on orders
| Query: UPDATE orders SET status = 'shipped' WHERE id = 42;
|
+--> Blocked: PID 3456 | Waiting: 2 min | Lock: AccessShareLock on orders
Query: SELECT * FROM orders WHERE id = 42;Wait Event Distribution:
| Wait Type | Count | Top Events |
|---|---|---|
| Lock | 5 | tuple (3), transactionid (2) |
| IO | 2 | DataFileRead (2) |
Recommendations:
idle_in_transaction_session_timeout = '60s'After resolving lock contention:
get_active_queries with include_idle: true to confirm no more blocking chainsanalyze_wait_events to confirm Lock wait events have decreased| Feature | Version |
|---|---|
idle_in_transaction_session_timeout | PG9.6+ |
REINDEX CONCURRENTLY | PG12+ |
Lock wait event type breakdown | PG9.6+ (detailed wait events) |
pg_blocking_pids() function | PG9.6+ |
pg_cancel_backend() attempts a graceful cancel (query stops, transaction stays open)pg_terminate_backend() forcefully terminates the session (use as last resort)autovacuum launcher unless absolutely necessarylock_timeout before DDL is a safe practice to prevent queue amplification~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.