PostgreSQL 프로세스 모델

PostgreSQL의 멀티프로세스 설계를 깊이 파고듭니다. Backend Process의 로컬 메모리(work_mem, maintenance_work_mem, temp_buffers)와 Shared Memory의 역할, 연결당 메모리 비용, 그리고 pg_stat_activity로 프로세스를 모니터링하는 방법을 정리합니다.

· 6 min read · PALDYN Team

지난 글에서 PostgreSQL의 전체 아키텍처를 개관했다. 이번에는 프로세스 모델을 더 깊이 파고든다. 특히 연결 하나가 얼마나 많은 메모리를 사용하는지, 그리고 메모리 파라미터를 어떻게 설계해야 하는지가 핵심이다.

fork() 기반 프로세스 생성

클라이언트가 PostgreSQL에 접속하면 다음 순서가 발생한다.

  1. 클라이언트가 TCP 5432 포트로 연결 요청
  2. Postmaster가 요청을 받아 fork() 시스템 콜로 자신을 복사
  3. 새로 생성된 Backend Process가 클라이언트와 1:1로 통신
  4. 클라이언트가 연결을 끊으면 Backend Process도 종료

이 방식은 Copy-on-Write(CoW) 덕분에 초기 메모리 복사 비용이 낮다. 그러나 연결이 늘어날수록 OS 프로세스가 증가해 Context Switching 비용이 높아진다. max_connections의 기본값이 100인 이유다.

메모리 영역의 두 종류

PostgreSQL 프로세스 모델 — 메모리 구조

공유 메모리 (Shared Memory)

모든 Backend Process가 공유하는 영역이다. 크기는 서버 기동 시 결정되어 실행 중 변경할 수 없다.

  • Shared Buffers: 테이블·인덱스 페이지의 캐시. 자주 접근하는 페이지를 메모리에 유지해 디스크 I/O를 줄인다.
  • WAL Buffers: WAL 레코드를 디스크에 쓰기 전 임시 저장.
  • Lock Table: 행·테이블·Advisory Lock 정보.
  • Proc Array: 모든 Backend의 트랜잭션 상태. MVCC의 Snapshot 계산에 사용.

로컬 메모리 (Local Memory)

각 Backend Process가 독점적으로 사용하는 메모리다. 연결당 독립적으로 할당된다.

파라미터용도기본값
work_mem정렬, 해시 조인, Bitmap Index Scan 등4MB
maintenance_work_memVACUUM, ANALYZE, CREATE INDEX64MB
temp_buffers임시 테이블 캐시8MB
-- 현재 메모리 파라미터 조회
SELECT name, setting, unit, source
FROM   pg_settings
WHERE  name IN ('shared_buffers', 'work_mem',
                'maintenance_work_mem', 'effective_cache_size');

-- 실행 계획 기반 work_mem 튜닝 (세션 단위)
SET work_mem = '256MB';
EXPLAIN ANALYZE SELECT * FROM orders ORDER BY amount DESC;
RESET work_mem;

work_mem의 함정 — 병렬 쿼리와 곱셈 효과

work_mem쿼리 하나당 이 금액이 아니라 노드 하나당 이 금액이다. 복잡한 쿼리는 여러 정렬/해시 노드를 가질 수 있고, 병렬 작업자(max_parallel_workers_per_gather)가 추가되면 실제 소비는:

실제 work_mem 소비 ≈ work_mem × 노드 수 × 병렬 작업자 수

연결 100개에서 각각 256MB를 쓰면 25GB가 된다. work_mem은 충분히 작게(4-16MB) 유지하고, 필요한 쿼리에서만 SET work_mem으로 세션 단위로 올리는 전략이 안전하다.

프로세스 · 메모리 모니터링 SQL

연결당 오버헤드

새 Backend Process 하나가 시작할 때 소비하는 메모리는 대략 10MB 수준이다. max_connections = 500으로 설정하면 연결만으로 5GB가 예약된다. 여기에 work_mem이 더해지므로 커넥션 풀러(PgBouncer) 를 도입해 실제 Backend 수를 줄이는 것이 운영의 핵심이다.

# PgBouncer 예시 (외부 풀러)
# 클라이언트 1000개 → PgBouncer → PostgreSQL 50개 연결
# transaction pooling 모드 권장

pg_stat_activity로 프로세스 모니터링

-- 상태별 연결 수
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;

-- 오래 실행 중인 쿼리 (10분 이상)
SELECT pid, now() - query_start AS duration,
       state, query
FROM   pg_stat_activity
WHERE  state != 'idle'
  AND  query_start < now() - INTERVAL '10 minutes'
ORDER  BY duration DESC;

-- 특정 프로세스 취소 (쿼리만 취소, 연결 유지)
SELECT pg_cancel_backend(pid)
FROM   pg_stat_activity
WHERE  pid = 12345;

-- 연결 자체 강제 종료
SELECT pg_terminate_backend(12345);

max_connections 설계

max_connections 공식 (경험칙):
  여유 RAM(GB) × 100 / (work_mem MB)

예: 32GB RAM, work_mem=4MB
  => (32 × 1024 - shared_buffers 8192) MB ÷ 4MB ÷ 10(여유) ≈ 600
  → max_connections = 200 (PgBouncer로 나머지 처리)

실제로는 shared_buffers를 전체 RAM의 25%, effective_cache_size를 75%로 설정하고, work_mem은 OLTP에서 4-16MB, 분석 쿼리는 256MB 이상으로 세션별 조정하는 패턴이 일반적이다.

정리

PostgreSQL의 프로세스 모델은 단순하지만 연결 수와 메모리 설계를 잘못하면 OOM이 발생한다. 핵심 원칙은 두 가지다. 첫째, max_connections를 작게 유지하고 커넥션 풀러를 사용한다. 둘째, work_mem은 기본값을 낮게 두고 필요할 때만 세션 단위로 올린다.


지난 글: PostgreSQL 아키텍처 개요

다음 글: Shared Buffers와 work_mem — PostgreSQL 메모리 심화


읽어주셔서 감사합니다. 😊