PostgreSQL 운영 중 자주 발생하는 문제와 해결 방법을 빈도순으로 정리합니다.
접속 오류
FATAL: password authentication failed
FATAL: password authentication failed for user "devuser"
| 원인 |
해결 방법 |
| 비밀번호 오류 |
비밀번호 재확인 |
| pg_hba.conf 설정 |
인증 방식 확인 (peer → scram-sha-256) |
| 롤 미존재 |
\du로 롤 존재 여부 확인 |
-- 비밀번호 재설정
ALTER ROLE devuser WITH PASSWORD 'NewPass123!';
FATAL: no pg_hba.conf entry for host
FATAL: no pg_hba.conf entry for host "192.168.1.100", user "devuser", database "mydb"
# pg_hba.conf에 접속 허용 규칙 추가
# host mydb devuser 192.168.1.0/24 scram-sha-256
# 설정 리로드
sudo systemctl reload postgresql
connection refused / could not connect to server
| 원인 |
해결 방법 |
| 서비스 미실행 |
sudo systemctl start postgresql |
| listen_addresses 설정 |
postgresql.conf에서 listen_addresses = '*' |
| 포트 불일치 |
기본 5432 확인 |
| 방화벽 차단 |
5432 포트 허용 |
테이블 부풀림 (Table Bloat)
PostgreSQL의 MVCC 특성상 UPDATE/DELETE가 많은 테이블은 크기가 계속 증가합니다.
증상
- 테이블 크기가 실제 데이터 대비 비정상적으로 큼
- 쿼리 성능이 점진적으로 저하
확인
-- 죽은 튜플 비율 확인
SELECT relname, n_live_tup, n_dead_tup,
ROUND(n_dead_tup::NUMERIC / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS dead_pct,
last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
해결
-- 일반 VACUUM (공간 재사용 표시, 서비스 중단 없음)
VACUUM ANALYZE large_table;
-- VACUUM FULL (물리적 공간 반환, 배타 락 주의)
VACUUM FULL large_table;
-- pg_repack (온라인 테이블 재구축, 확장 설치 필요)
-- pg_repack -U postgres -d mydb -t large_table
락 대기 / 쿼리 멈춤
락 대기 확인
SELECT
blocked.pid AS blocked_pid,
blocked_activity.usename AS blocked_user,
blocked_activity.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking_activity.usename AS blocking_user,
blocking_activity.query AS blocking_query,
NOW() - blocked_activity.query_start AS wait_duration
FROM pg_catalog.pg_locks blocked
JOIN pg_catalog.pg_locks blocking
ON blocking.locktype = blocked.locktype
AND blocking.relation = blocked.relation
AND blocking.pid != blocked.pid
JOIN pg_stat_activity blocked_activity ON blocked_activity.pid = blocked.pid
JOIN pg_stat_activity blocking_activity ON blocking_activity.pid = blocking.pid
WHERE NOT blocked.granted;
해결
-- 블로킹 쿼리 취소 (graceful)
SELECT pg_cancel_backend(blocking_pid);
-- 블로킹 세션 강제 종료
SELECT pg_terminate_backend(blocking_pid);
-- 락 타임아웃 설정 (예방)
SET lock_timeout = '10s';
디스크 용량 부족
ERROR: could not extend file "base/16384/12345": No space left on device
확인
-- 데이터베이스별 크기
SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database ORDER BY pg_database_size(datname) DESC;
-- 대용량 테이블
SELECT relname,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;
해결
| 방법 |
명령어 |
| WAL 파일 정리 |
pg_archivecleanup 또는 max_wal_size 조정 |
| 불필요한 데이터 삭제 |
DELETE + VACUUM |
| 테이블 부풀림 해소 |
VACUUM FULL 또는 pg_repack |
| 로그 파일 정리 |
로그 로테이션 설정 |
| 임시 파일 정리 |
temp_file_limit 설정 |
슬로우 쿼리 / 성능 저하
점검 체크리스트
| 순서 |
점검 항목 |
확인 방법 |
| 1 |
현재 활성 쿼리 |
pg_stat_activity |
| 2 |
락 대기 여부 |
pg_locks |
| 3 |
실행 계획 확인 |
EXPLAIN ANALYZE |
| 4 |
통계 정보 갱신 |
ANALYZE table_name |
| 5 |
죽은 튜플 확인 |
pg_stat_user_tables |
| 6 |
캐시 히트율 |
pg_stat_database |
| 7 |
디스크 I/O |
OS 레벨 iostat 확인 |
-- 장시간 실행 중인 쿼리
SELECT pid, usename, state,
NOW() - query_start AS duration,
LEFT(query, 100) AS query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY duration DESC;
-- 통계 갱신 (실행 계획이 갑자기 바뀐 경우)
ANALYZE employees;
too many connections
FATAL: too many connections for role "devuser"
FATAL: sorry, too many clients already
해결
-- 현재 접속 수 확인
SELECT count(*) FROM pg_stat_activity;
SELECT usename, count(*) FROM pg_stat_activity GROUP BY usename;
-- max_connections 확인
SHOW max_connections;
-- 유휴 세션 종료
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
AND query_start < NOW() - INTERVAL '30 minutes'
AND pid != pg_backend_pid();
근본 해결
# postgresql.conf
max_connections = 200
# 커넥션 풀러 사용 권장 (PgBouncer)
# PgBouncer는 수천 개의 애플리케이션 연결을 수십 개의 DB 연결로 관리
인코딩 / 한글 문제
ERROR: character with byte sequence 0xec 0x95 0x88 in encoding "UTF8" has no equivalent in encoding "LATIN1"
확인
-- 데이터베이스 인코딩 확인
SELECT datname, pg_encoding_to_char(encoding) AS encoding, datcollate
FROM pg_database;
-- 클라이언트 인코딩 확인
SHOW client_encoding;
해결
-- 클라이언트 인코딩 변경
SET client_encoding = 'UTF8';
-- 데이터베이스 생성 시 인코딩 지정
CREATE DATABASE mydb
ENCODING = 'UTF8'
LC_COLLATE = 'ko_KR.UTF-8'
LC_CTYPE = 'ko_KR.UTF-8'
TEMPLATE = template0;
SERIALIZABLE 격리 수준 오류
ERROR: could not serialize access due to concurrent update
ERROR: could not serialize access due to read/write dependencies among transactions
SERIALIZABLE 격리 수준에서 동시 트랜잭션 충돌 시 발생합니다.
해결
# 애플리케이션에서 재시도 로직 구현
MAX_RETRIES = 3
for attempt in range(MAX_RETRIES):
try:
cursor.execute("BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE")
cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
cursor.execute("COMMIT")
break
except psycopg2.errors.SerializationFailure:
cursor.execute("ROLLBACK")
time.sleep(0.1 * (attempt + 1))
OOM Killer (Linux)
메모리 부족 시 OS가 PostgreSQL 프로세스를 강제 종료합니다.
확인
dmesg | grep -i "oom\|killed"
grep -i "oom" /var/log/syslog
해결
| 방법 |
설명 |
| shared_buffers 축소 |
전체 메모리의 25% 이하로 |
| work_mem 축소 |
세션당 메모리 제한 |
| max_connections 축소 |
동시 접속 수 제한 |
| huge_pages 활성화 |
메모리 효율 개선 |
# PostgreSQL 프로세스의 OOM 점수 낮추기
echo -1000 > /proc/$(head -1 /var/lib/postgresql/17/main/postmaster.pid)/oom_score_adj
복제 (Replication) 오류
복제 상태 확인
-- Primary에서 확인
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes
FROM pg_stat_replication;
-- Standby에서 확인
SELECT pg_is_in_recovery(); -- true면 Standby
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();
복제 지연 해결
| 방법 |
설명 |
| max_wal_senders 증가 |
WAL 전송 프로세스 수 |
| wal_keep_size 증가 |
WAL 보존 크기 |
| Standby 성능 개선 |
디스크 I/O, 메모리 확인 |
| hot_standby_feedback |
Standby에서 VACUUM 충돌 방지 |
트러블슈팅 필수 명령어 요약
-- 현재 활성 세션
SELECT pid, usename, state, query_start, LEFT(query, 80) FROM pg_stat_activity WHERE state = 'active';
-- 락 확인
SELECT pid, relation::regclass, mode, granted FROM pg_locks WHERE NOT granted;
-- 쿼리 취소 / 세션 종료
SELECT pg_cancel_backend(pid);
SELECT pg_terminate_backend(pid);
-- 데이터베이스 크기
SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database;
-- 테이블 통계
SELECT relname, n_live_tup, n_dead_tup, last_autovacuum FROM pg_stat_user_tables;
-- 로그 파일 위치
SHOW log_directory;
SHOW data_directory;
-- 설정 리로드
SELECT pg_reload_conf();
관련된 글 (postgresql > lecture-postgresql)