PostgreSQL 교재 · 23편 / 24편

성능 튜닝 — shared_buffers·work_mem·VACUUM

설정 파라미터·autovacuum·bloat 잡기.

고급읽는 시간 8분2026-05-17
PostgreSQL 의 메모리·디스크·VACUUM 파라미터가 톱니바퀴처럼 맞물린 도식

"느린 SELECT 가 있다" 면 EXPLAIN(17편)·인덱스(16편) 가 먼저. "전반적으로 느린다" 면 설정 파라미터·autovacuum·bloat 가 의심. 23편은 운영 DB 의 건강을 유지하는 설정·청소 작업.

핵심 메모리 파라미터 — 5개

파라미터역할권장
shared_buffersPG 의 페이지 캐시RAM 의 25%
effective_cache_sizeOS+PG 캐시 추정 (옵티마이저 힌트)RAM 의 50-75%
work_mem정렬·해시당 메모리4MB~64MB (워크로드 따라)
maintenance_work_memVACUUM·CREATE INDEX 용256MB~2GB
wal_buffersWAL 메모리 버퍼기본(16MB) 보통 OK
# postgresql.conf 예시 (16GB RAM 서버)
shared_buffers = 4GB
effective_cache_size = 12GB
work_mem = 16MB
maintenance_work_mem = 1GB
wal_buffers = 16MB

work_mem 함정. 이 값은 쿼리당이 아니라 정렬/해시 노드당. 한 쿼리에 GROUP BY + JOIN + ORDER BY 가 있으면 work_mem × 3. 동시 100 커넥션 × 큰 work_mem = 메모리 폭주. 모니터링 후 조정.

커넥션 관련

max_connections = 100              # 동시 커넥션 최대
# 너무 크면 컨텍스트 스위치·메모리↑

# PgBouncer 같은 풀러 사용 권장 — 앱이 1000 커넥션이어도
# PG 에는 100 만 도달

WAL·체크포인트

wal_level = replica              # 또는 logical
max_wal_size = 1GB               # 체크포인트 트리거 크기
min_wal_size = 80MB
checkpoint_timeout = 5min        # 강제 체크포인트 주기
checkpoint_completion_target = 0.9   # IO 분산

autovacuum — DB 의 자동 청소

왜 VACUUM 이 필요한가. PG 는 MVCC — UPDATE/DELETE 는 옛 row 를 즉시 지우지 않고 "죽음" 표시만. 시간이 지나면 dead row 가 쌓여 테이블·인덱스 크기 ↑(bloat). VACUUM 이 이를 회수.

# autovacuum 기본 켜져있음 — 보통 그대로 두면 됨
autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = 1min

# 트리거 임계값 (테이블 단위)
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2   # 20% 변경되면 VACUUM
autovacuum_analyze_scale_factor = 0.1  # 10% 변경되면 ANALYZE

큰 테이블의 autovacuum 조정

-- 1억 행 테이블에서 20% = 2천만 행 변경 후에야 VACUUM
-- 너무 늦음 → 테이블별로 더 자주
ALTER TABLE big_table SET (
  autovacuum_vacuum_scale_factor = 0.02,   -- 2% 만 변경돼도
  autovacuum_analyze_scale_factor = 0.01
);

-- 반대로 거의 INSERT 만 되는 로그 테이블 — VACUUM 부담 줄이기
ALTER TABLE logs SET (autovacuum_vacuum_scale_factor = 0.5);

bloat 확인 — 죽은 행이 얼마나

-- 테이블의 dead row 수
SELECT relname,
       n_live_tup,
       n_dead_tup,
       round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,
       last_autovacuum
FROM   pg_stat_user_tables
ORDER BY dead_pct DESC NULLS LAST
LIMIT 20;

-- dead_pct > 20% 면 VACUUM 부족 신호

수동 VACUUM

-- 일반 VACUUM (락 가벼움 — 운영 OK)
VACUUM users;
VACUUM ANALYZE users;       -- + 통계 갱신

-- VACUUM FULL (전체 재작성 — ACCESS EXCLUSIVE 락, 운영 시 위험)
VACUUM FULL users;

-- 대안 — pg_repack 확장 (락 없이 bloat 정리)
pg_repack -d mydb -t users

VACUUM FULL 의 비용. 테이블 전체에 ACCESS EXCLUSIVE 락 + 디스크에 통째로 새로 씀. 큰 테이블이면 수십 분~수 시간 + 모든 작업 차단. 운영에서는 거의 안 씀. pg_repack(별도 설치) 이 대안 — 락 없이 백그라운드로.

ANALYZE — 통계 갱신

-- 옵티마이저는 통계 정보로 계획 결정
-- 통계가 오래되면 잘못된 계획 (17편)
ANALYZE big_table;

-- 컬럼별 (큰 테이블에서 일부만)
ANALYZE big_table (created_at);

-- 통계 샘플 크기 조정
ALTER TABLE big_table ALTER COLUMN status SET STATISTICS 500;
-- 기본 100 → 500 (더 정확)

connection·query 별 진단

-- 현재 활성 쿼리 (15편 다시)
SELECT pid, usename, state, query_start, query
FROM   pg_stat_activity
WHERE  state != 'idle'
ORDER BY query_start;

-- 가장 느린 쿼리 (pg_stat_statements 필요)
SELECT round(total_exec_time::numeric, 0) AS total_ms,
       calls,
       round(mean_exec_time::numeric, 2)  AS mean_ms,
       query
FROM   pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

-- 캐시 히트율 (90% 미만이면 shared_buffers 부족)
SELECT
  sum(heap_blks_hit) * 100.0 / NULLIF(sum(heap_blks_hit + heap_blks_read), 0) AS hit_ratio
FROM pg_statio_user_tables;

실전 체크리스트 — 새 서버 셋업

  1. shared_buffers = RAM × 25%.
  2. effective_cache_size = RAM × 60%.
  3. work_mem = 16MB 시작, EXPLAIN ANALYZE 로 조정.
  4. maintenance_work_mem = 1GB (CREATE INDEX 빠름).
  5. max_connections = 100 + PgBouncer 풀러.
  6. checkpoint_timeout = 15min(대량 쓰기) ~ 5min(기본).
  7. autovacuum on, 큰 테이블은 scale_factor 0.02~0.05.
  8. pg_stat_statements 확장 + 정기 점검.
  9. 주기적 ANALYZE (autovacuum 보완).
  10. 모니터링 — Prometheus + postgres_exporter + Grafana.

도구 추천. pgtune.leopard.in.ua — RAM·워크로드 입력 → 추천 conf 생성. 초기 셋업의 좋은 출발점.

24편 — 확장 (pgvector·PostGIS) [최종편]

24편 시리즈의 마지막. PG 의 확장 생태계와 LLM 시대의 pgvector.

📚 PostgreSQL 배우기 교재
이전: 22편 파티셔닝 · 현재: 23편 (고급) · 다음 → 24편 확장 (시리즈 마지막) · 진행: 23/24

© 2026 주나이테크(주) @JUNAITECH