Haru Utils

POSTGRESQL TRIAGE

PostgreSQL 연결 고갈·느린 쿼리·락·복제 지연

SQL 원문이나 비밀번호를 노출하지 않고 연결 슬롯, wait event·blocking PID, query ID 통계와 WAL 위치를 같은 시각에 비교해 연결 고갈·느린 실행·락 경합·복제 지연을 구분합니다.
PostgreSQL too many clientsremaining connection slotsPostgres slow querypg_blocking_pidsPostgreSQL lock waitpg_stat_replication lagWAL replay delaypg_stat_statements
환경PostgreSQL 14~18 · libpq service와 0600 패스워드 파일 · pg_monitor 또는 승인된 최소 통계 조회 권한
분류데이터베이스
검토일2026-09-04
진행5단계 · 조회 우선

SAFE OPERATING BOUNDARY

중단·복구 기준부터 확인하세요

STOP CONDITIONS

여기서는 멈추세요

  • PGSERVICE 대상·primary/standby 역할·장애 시각을 확정하지 못했다면 세션 취소·종료·replay 변경을 실행하지 않습니다.
  • PID의 query_id·application_name·transaction 소유자와 rollback 영향을 확인하지 못했다면 pg_cancel_backend 또는 pg_terminate_backend를 실행하지 않습니다.
  • 비밀번호·connection URI·SQL literal·개인정보를 출력하거나 공유해야만 진단할 수 있는 상황이면 중단하고 DBA의 최소 권한·마스킹 수집 절차를 사용합니다.
  • 복제 slot 삭제·standby promote·restart·max_connections 변경이 필요해 보이면 즉시 조치하지 말고 RPO/RTO와 failover runbook을 가진 복제 담당자에게 이관합니다.
ROLLBACK

복구 기준

application pool·timeout·release 변경은 기록한 이전 정상 설정과 표준 배포 절차로 되돌립니다. pg_cancel_backend로 취소된 query는 자동 복구되지 않으며 pg_terminate_backend는 열린 transaction을 rollback하므로 업무 idempotency와 외부 side effect를 확인합니다. replay resume은 데이터 변경을 되돌리는 명령이 아니며, 원래 승인된 지연 복제 목적이 있을 때만 담당자 확인 후 별도 절차로 pause를 복구합니다.

ESCALATION PACK

담당자에게 전달할 자료

  • 동일 시각의 연결 상태별 집계, wait event, blocking PID·query_id와 max/reserved connection 설정
  • pg_stat_statements queryid별 calls·total/mean time·block 통계와 민감 SQL을 제거한 오류 코드 타임라인
  • primary sent/write/flush/replay LSN·byte lag와 standby WAL receiver·receive/replay LSN·pause 상태
  • 승인된 PID·조치자·조치 시각, 변경 전후 application error rate·업무 transaction 검증 결과
2026년 9월 4일 기준 PostgreSQL·Kubernetes·OpenSSL·NGINX·Certbot 공식 문서를 대조했습니다. 진단은 읽기 전용 명령과 최소 권한 계정으로 시작하고, 취소·세션 종료·설정 적용·reload는 대상·영향·복구 경로를 기록해 담당자가 승인한 경우에만 실행합니다. <...> 자리표시자는 검토된 리터럴 값으로 직접 치환하며 외부 입력으로 shell 명령을 조립하지 않습니다.

BEFORE YOU START

이런 증상에서 시작합니다

  • FATAL: remaining connection slots are reserved 또는 too many clients 오류가 발생함
  • 요청 지연과 함께 Lock·LWLock·Client 계열 wait event가 늘어남
  • 긴 transaction과 blocking PID가 확인되거나 동일 query ID 실행 시간이 급증함
  • standby의 replay LSN이 뒤처지고 읽기 복제본 데이터 반영이 늦음

CHECK THE BRANCH

놓치기 쉬운 원인 분기

01

연결 슬롯이 고갈된 경우

max_connections만 올리기 전에 상태별·DB별·사용자별·application_name별 연결 수와 pool 설정을 확인합니다. idle in transaction은 단순 idle과 달리 락과 vacuum 진행을 방해할 수 있습니다.

02

느린 쿼리 또는 락 경합인 경우

query 원문 대신 query_id, 실행 시간, wait event와 pg_blocking_pids를 사용해 범위를 좁힙니다. query_id는 compute_query_id 설정과 pg_stat_statements·query ID 계산 상태에 따라 NULL일 수 있으므로 NULL을 정상·이상 판정으로 단정하지 않습니다. pg_stat_statements가 사전에 활성화되지 않았다면 장애 중 재시작해 켜지 않습니다.

03

물리 복제 지연인 경우

sent·write·flush·replay LSN의 바이트 차이와 WAL receiver 상태를 함께 봅니다. write_lag·replay_lag는 idle 시 NULL이 될 수 있고 따라잡을 예상 시간을 뜻하지 않습니다.

04

여러 증상이 동시에 보이는 경우

장기 transaction이 WAL 보존과 락을 늘리고 과도한 연결이 CPU·I/O를 악화할 수 있습니다. 동일 시각 스냅샷을 남긴 뒤 최초 원인과 2차 증상을 분리합니다.

FOLLOW THE FLOW

순서대로 확인하기

1
조회시스템을 변경하지 않는 확인 단계

대상·역할과 연결·대기·복제 상태 관찰

비밀번호를 명령행이나 URI에 넣지 않습니다. 사전에 검토한 libpq service와 0600 패스워드 파일을 사용하고, SQL 원문을 조회하지 않는 집계부터 실행합니다.

접속 대상·서버 역할
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT version(), current_database(), pg_is_in_recovery(), clock_timestamp();"
상태별 연결 집계
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT datname, usename, application_name, state, count(*) AS sessions, max(clock_timestamp() - state_change) AS oldest_state FROM pg_stat_activity WHERE pid <> pg_backend_pid() GROUP BY datname, usename, application_name, state ORDER BY sessions DESC;"
DB·role·application_name은 내부 식별정보입니다. 외부 공유본에서는 마스킹합니다.
대기 유형 집계
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT coalesce(wait_event_type, 'CPU_or_not_waiting') AS wait_type, coalesce(wait_event, '-') AS wait_event, count(*) AS sessions FROM pg_stat_activity WHERE pid <> pg_backend_pid() GROUP BY wait_event_type, wait_event ORDER BY sessions DESC;"
primary 복제 연결 요약
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT application_name, state, sync_state, sent_lsn, write_lsn, flush_lsn, replay_lsn, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replay_byte_lag, write_lag, flush_lag, replay_lag, reply_time FROM pg_stat_replication ORDER BY application_name;"
결과 읽기

연결 수만 높으면 pool·누수 분기를, blocking PID와 Lock wait가 있으면 락 분기를, primary에서 replay LSN 차이가 크거나 standby receiver가 비정상이면 복제 분기를 우선합니다.

다음 판단

설정 상한과 PID 단위 대기·blocking 관계를 읽기 전용으로 확인합니다.

2
조회시스템을 변경하지 않는 확인 단계

연결·락·느린 query ID·WAL 구간 판별

각 분기는 필요한 명령만 실행합니다. 통계 view 권한이 부족하면 superuser 비밀번호를 공유받지 말고 DBA에게 최소 pg_monitor 또는 pg_read_all_stats 역할로 수집을 요청합니다.

연결 한도와 예약 슬롯
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT current_setting('max_connections') AS max_connections, current_setting('superuser_reserved_connections') AS superuser_reserved, current_setting('reserved_connections', true) AS reserved_connections;"
waiting session과 blocker
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT pid, datname, usename, application_name, state, wait_event_type, wait_event, clock_timestamp() - xact_start AS xact_age, clock_timestamp() - query_start AS query_age, query_id, pg_blocking_pids(pid) AS blocking_pids FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND (wait_event_type IS NOT NULL OR state = 'idle in transaction') ORDER BY xact_start NULLS LAST;"
query 열은 의도적으로 조회하지 않습니다. PID·query_id와 업무 소유자 기록으로 정확한 세션을 확인합니다.
query ID별 누적 실행 통계
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT queryid, calls, round(total_exec_time::numeric, 1) AS total_ms, round(mean_exec_time::numeric, 1) AS mean_ms, rows, shared_blks_read, temp_blks_written FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;"
pg_stat_statements가 이미 설치·preload된 DB에서만 사용합니다. SQL 원문인 query 열은 선택하지 않습니다.
standby receiver·replay 상태
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT pg_is_in_recovery(), pg_is_wal_replay_paused(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn(), pg_last_xact_replay_timestamp(); SELECT status, sender_host, sender_port, slot_name, latest_end_lsn, latest_end_time, last_msg_receipt_time FROM pg_stat_wal_receiver;"
결과 읽기

blocking_pids가 비어 있고 유효한 query_id의 누적 시간만 크면 실행 계획·I/O 분기, blocker가 있으면 transaction 소유자 분기입니다. query_id가 NULL이면 compute_query_id·extension 상태와 다른 증거를 확인하고 임의로 같은 SQL을 묶지 않습니다. receive LSN은 전진하지만 replay LSN이 멈추면 replay·standby 부하를, 둘 다 멈추면 sender·network·slot을 봅니다.

다음 판단

취소·종료 또는 replay resume 전에 로그·timeout·pool·배포 변경 이력을 확보합니다.

3
주의서버 부하나 권한을 고려할 단계

민감 SQL 없이 원인 증거와 변경 전 상태 보존

로그에는 SQL literal과 개인정보가 포함될 수 있습니다. 제한된 터미널에서 오류 코드와 시각 주변만 확인하고, 외부 전달본은 SQL·client address·role을 마스킹합니다.

timeout·통계 설정
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SHOW statement_timeout; SHOW lock_timeout; SHOW idle_in_transaction_session_timeout; SHOW deadlock_timeout; SELECT extversion FROM pg_extension WHERE extname = 'pg_stat_statements';"
PostgreSQL 오류 코드 주변
journalctl -u <POSTGRESQL_SERVICE>.service --since '<INCIDENT_START>' --until '<INCIDENT_END>' --no-pager | grep -Ei -C 3 'too many clients|remaining connection slots|deadlock detected|lock timeout|canceling statement|replication|wal receiver|could not receive data'
log_statement나 log_min_duration_statement가 켜진 환경은 SQL·literal이 섞일 수 있으므로 공유 전 반드시 마스킹합니다.
application pool·배포 변경 비교
git -C <APPLICATION_REPOSITORY> diff <LAST_KNOWN_GOOD_REVISION> -- <REVIEWED_POOL_AND_DATABASE_CONFIG_PATH>
비밀번호·DSN secret이 추적되는 저장소라면 실행하지 말고 secret 값이 제거된 설정 diff를 담당자에게 요청합니다.
현재 관찰값 제한 보관
install -d -m 0700 <APPROVED_EVIDENCE_DIR>
umask 077
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --tuples-only --no-align --command "SELECT clock_timestamp(), count(*) FILTER (WHERE state = 'active'), count(*) FILTER (WHERE state = 'idle in transaction'), count(*) FILTER (WHERE wait_event_type = 'Lock') FROM pg_stat_activity;" > <APPROVED_EVIDENCE_DIR>/postgresql-incident-counts.txt
chmod 0600 <APPROVED_EVIDENCE_DIR>/postgresql-incident-counts.txt
결과 읽기

장기 transaction 소유자, connection pool 상한 합계, query ID 통계, 복제 LSN 구간과 직전 배포를 한 타임라인에 놓습니다. 로그에서 본 SQL literal을 티켓에 그대로 붙이지 않습니다.

다음 판단

업무 소유자와 DBA가 승인한 최소 조치 한 가지만 선택합니다.

4
변경데이터·서비스 상태가 달라질 수 있는 단계

승인된 단일 세션·pool·replay 최소 조치

대상 PID·업무 영향·transaction rollback 가능성을 확인합니다. max_connections 즉시 증설, PostgreSQL 재시작, replication slot 삭제, standby promote는 이 가이드의 즉시 조치가 아닙니다.

변경 단계입니다. 실행 전 대상 이름과 경로, 서비스 중단 영향, 복구 방법을 다시 확인하세요.
A. 승인된 query 한 건 취소
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT pg_cancel_backend(<APPROVED_PID>);"
PID의 query_id·application_name·transaction 소유자를 재확인한 뒤 실행합니다. 세션은 유지되고 현재 query만 취소 요청을 받습니다.
B. 승인된 session 한 건 종료
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT pg_terminate_backend(<APPROVED_PID>, 5000);"
cancel이 실패하고 열린 transaction을 rollback해도 되는 세션에만 사용합니다. connection pool이 즉시 다시 채우는지 함께 감시합니다.
C. 의도치 않게 멈춘 standby replay 재개
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT pg_wal_replay_resume();"
해당 standby가 운영 절차상 의도적으로 pause된 것이 아님을 복제 담당자가 확인한 경우에만 실행합니다.
D. 검토된 pool 설정 배포 전 확인
git -C <APPLICATION_REPOSITORY> diff --check <LAST_KNOWN_GOOD_REVISION> -- <REVIEWED_POOL_AND_DATABASE_CONFIG_PATH>
pool 상한 합계가 DB의 일반 연결 예산 안에 있고 timeout·재시도 폭주 방지가 검토된 release만 표준 배포 절차로 적용합니다.
결과 읽기

cancel은 query 취소, terminate는 session과 열린 transaction 종료, replay_resume은 standby apply 재개입니다. 서로 대체 관계가 아니며 원인에 맞지 않는 명령을 함께 실행하지 않습니다.

다음 판단

동일 조회를 반복해 연결 여유·wait·WAL 진전과 업무 정확성을 확인하고, 악화되면 승인된 이전 상태로 복구합니다.

5
변경데이터·서비스 상태가 달라질 수 있는 단계

연결·락·query ID·복제 회복 검증과 롤백 판단

변경 직후와 정상 트래픽 구간을 나눠 확인합니다. 숫자가 잠깐 좋아진 것만으로 종료하지 말고 application error rate와 transaction 정확성도 함께 검증합니다.

변경 단계입니다. 실행 전 대상 이름과 경로, 서비스 중단 영향, 복구 방법을 다시 확인하세요.
연결·대기 재검증
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT state, wait_event_type, wait_event, count(*) FROM pg_stat_activity WHERE pid <> pg_backend_pid() GROUP BY state, wait_event_type, wait_event ORDER BY count(*) DESC;"
blocking 재검증
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT pid, application_name, query_id, pg_blocking_pids(pid) AS blocking_pids, clock_timestamp() - query_start AS query_age FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0 ORDER BY query_start;"
primary WAL lag 재검증
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT application_name, state, sync_state, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replay_byte_lag, replay_lag, reply_time FROM pg_stat_replication ORDER BY application_name;"
standby replay 진전 재검증
psql "service=<PGSERVICE>" -X --set=ON_ERROR_STOP=1 --command "SELECT pg_is_wal_replay_paused(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn(), pg_last_xact_replay_timestamp(), clock_timestamp();"
결과 읽기

새 연결이 안정적으로 수용되고 blocking chain이 해소되며 query ID latency와 WAL byte lag가 정상 기준으로 돌아와야 합니다. idle primary의 lag interval NULL은 단독 실패 신호가 아닙니다.

다음 판단

pool·release 변경은 이전 정상 버전으로 되돌립니다. 취소된 query는 자동 재실행하지 않고 업무 idempotency를 확인하며, 종료된 transaction은 rollback 여부를 확인해 보정합니다. replay를 다시 pause하는 것은 원래 승인 목적이 명확한 경우에만 별도 승인합니다.

PRIMARY REFERENCES

공식 문서

배포판과 버전에 따라 옵션·로그 위치가 다를 수 있습니다. 실행 전 서버의 --help와 로컬 매뉴얼을 함께 확인하세요.

도구 빠른 검색

최근 사용한 도구를 다시 열거나, 이름과 기능으로 검색하세요.

검색어와 도구의 입력·결과는 저장하지 않습니다.

↑↓ 이동 · Enter 열기 · Esc 닫기