15.3 락 대기·세션 문제
PostgreSQL 사고의 절반은 락 대기·세션 폭주. 진단 SQL과 즉시 대응 절차를 정리합니다.
대기 세션 진단
SELECT pid, usename, application_name, client_addr,
state, wait_event_type, wait_event,
xact_start, now() - xact_start AS xact_age,
query_start, now() - query_start AS query_age,
LEFT(query, 100) AS query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY xact_start NULLS LAST;| 컬럼 | 의미 |
|---|---|
wait_event_type | Lock/LWLock/IO/Client/Activity |
wait_event | 구체 — transactionid, relation, BufferContent 등 |
xact_age | 트랜잭션 길이 — 5분 이상이면 알람 |
블로킹 그래프
SELECT bl.pid AS blocked, bl.usename AS u1, bl.query AS blocked_query,
kl.pid AS blocker, kl.usename AS u2, kl.query AS blocker_query
FROM pg_stat_activity bl
JOIN LATERAL (
SELECT * FROM pg_stat_activity
WHERE pid = ANY(pg_blocking_pids(bl.pid))
) kl ON true
WHERE cardinality(pg_blocking_pids(bl.pid)) > 0;체인 추적:
WITH RECURSIVE chain AS (
SELECT pid, pg_blocking_pids(pid) AS blockers, 0 AS depth
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0
UNION ALL
SELECT a.pid, pg_blocking_pids(a.pid), c.depth + 1
FROM chain c
JOIN pg_stat_activity a ON a.pid = ANY(c.blockers)
WHERE c.depth < 5
)
SELECT * FROM chain;대기 트리의 root = 사고의 원인입니다.
즉시 대응 — pg_cancel / pg_terminate
SELECT pg_cancel_backend(<pid>); -- 현재 쿼리 취소
SELECT pg_terminate_backend(<pid>); -- 세션 강제 종료| 명령 | 효과 |
|---|---|
pg_cancel_backend | 현재 SQL만 cancel. 트랜잭션은 살아 있음 |
pg_terminate_backend | 트랜잭션 전체 종료. connection 끊김 |
우선순위: cancel 먼저, 안 풀리면 terminate.
흔한 락 시나리오
A. ALTER TABLE이 SELECT 차단
ALTER TABLE orders ADD COLUMN ...ACCESS EXCLUSIVE — 모든 SELECT 차단합니다. 운영 중 큰 ALTER가 수 분~수 시간 동안 클러스터 정지처럼 보이는 사례.
대응:
- 짧은 lock_timeout으로 ALTER 자체 취소
SET lock_timeout = '5s';
ALTER TABLE orders ADD COLUMN ...; -- 5초 안 잡히면 ERROR- 작업 분리 —
ADD COLUMN NULLABLE(메타데이터만, PG 11+ 빠름) + 별도 UPDATE
B. row-level lock 폭주
UPDATE accounts SET balance = ... WHERE id = popular_id
-- 같은 popular row를 100 트랜잭션이 동시에대응:
- application 측 row-level 동시성 줄임 (advisory lock으로 직렬화)
- 데이터 모델 검토 — 핫 row를 여러 partition으로 분할
SKIP LOCKED로 queue 패턴
C. autovacuum이 락 잡고 있음
SELECT pid, query, wait_event FROM pg_stat_activity
WHERE backend_type = 'autovacuum worker';대부분 SHARE UPDATE EXCLUSIVE만 — DDL이 아니면 비차단. 그러나 VACUUM FULL이면 ACCESS EXCLUSIVE — 사고입니다.
대응: autovacuum worker를 cancel하면 빠르게 해제.
D. 잊혀진 prepared transaction
SELECT * FROM pg_prepared_xacts;비어 있어야 정상. row가 있고 prepared 시간이 옛날이면 ROLLBACK PREPARED.
세션 폭주
max_connections 한계
FATAL: too many connections-- 사용자별 연결 수
SELECT usename, count(*)
FROM pg_stat_activity
GROUP BY usename
ORDER BY count(*) DESC;
-- application별
SELECT application_name, count(*)
FROM pg_stat_activity
GROUP BY application_name;
-- state별
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;idle in transaction이 많으면 application connection leak. idle이 많으면 풀 크기 너무 큽니다.
대응:
- pgBouncer 도입(13.4)
- application connection lifecycle 점검
idle_in_transaction_session_timeout
LWLock 경합
SELECT wait_event, count(*) FROM pg_stat_activity
WHERE wait_event_type = 'LWLock'
GROUP BY wait_event;| LWLock | 원인 |
|---|---|
WALInsert | 매우 높은 commit TPS |
BufferContent | hot row 동시 수정 |
ProcArray | 매우 많은 연결 (스냅샷 계산) |
SubtransSLRU | SAVEPOINT 폭주 (14.4) |
WALWrite | WAL fsync 한계 |
WALWriteLock | 같음 |
대응:
- WALInsert·WALWrite: WAL 디스크 분리, sync 모드 조정
- ProcArray: 연결 수 줄임 (pgBouncer)
- SubtransSLRU: SAVEPOINT 정리
advisory lock 진단
SELECT pid, locktype, classid, objid, mode, granted, query
FROM pg_locks l
JOIN pg_stat_activity a USING (pid)
WHERE locktype = 'advisory';pg_advisory_unlock_all() 호출이 빠진 application이면 세션 단위 lock이 누적.
운영 알람
| 지표 | WARN | CRIT |
|---|---|---|
pg_blocking_pids 응답 있는 PID 수 | 5 | 20 |
| 트랜잭션 5분 초과 | 1개 | — |
| 트랜잭션 30분 초과 | — | 1개 |
idle in transaction 1분 | 5개 | 20개 |
| total active connections | max_connections × 70% | × 90% |
LWLock wait 비중 | 일시 | 지속 |
사고 절차
- 알람 → 대기 세션 목록 캡처
- 블로킹 트리 식별
- root blocker 결정
- cancel·terminate (필요 시)
- 트랜잭션이 다시 진행 확인
- 사후 분석 — 왜 발생했나, 예방
운영 체크리스트
-- 한 번에 모든 진단
SELECT 'long tx' AS issue, count(*) FROM pg_stat_activity WHERE xact_start < now() - interval '5 min' AND state != 'idle'
UNION ALL
SELECT 'idle in tx', count(*) FROM pg_stat_activity WHERE state = 'idle in transaction' AND state_change < now() - interval '5 min'
UNION ALL
SELECT 'blocked', count(*) FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0
UNION ALL
SELECT 'prepared', count(*) FROM pg_prepared_xacts
UNION ALL
SELECT 'replication slot inactive', count(*) FROM pg_replication_slots WHERE NOT active;이 한 번의 쿼리로 주요 위험 신호를 잡습니다.
pg_terminate_backend는 마지막 수단. application·DBA에게 미리 알린 후. 자동 모니터링이 self-healing으로 자동 terminate한다면 알람·로깅 포함합니다.정리
pg_blocking_pids()+pg_stat_activity가 락 진단의 핵심- ALTER TABLE의 ACCESS EXCLUSIVE가 가장 흔한 차단
- 잊혀진 prepared transaction은 영구 차단
- LWLock 경합은 공유 자원 한계 — 별도 분리·풀러 대응
- 사고 시 cancel → terminate 순서, 사후 분석 필수
다음 절(15.4)에서는 디스크·메모리 자원 사고 — 자원 부족을 봅니다.