PostgreSQL 아키텍처

[!tldr] 한줄 요약 PostgreSQL은 멀티프로세스 모델로 동작하며, Postmaster가 클라이언트 연결마다 Backend 프로세스를 fork하고, 백그라운드 프로세스들이 WAL 기록·체크포인트·VACUUM 등을 처리한다.

멀티프로세스 모델

PostgreSQL은 스레드가 아닌 프로세스 단위로 동작한다. 하나의 연결 실패가 다른 연결에 영향을 주지 않도록 장애 격리(Fault Isolation)를 우선한 설계다.

멀티프로세스 (PostgreSQL)멀티스레드 (MySQL)
장애 격리프로세스 단위로 격리스레드 하나가 전체에 영향 가능
메모리 효율낮음 (프로세스별 할당)높음 (메모리 공유)
연결 비용fork()로 높음스레드 생성으로 낮음
보완책PgBouncer 등 커넥션 풀링스레드풀

역사적으로도 Postgres가 1986년 시작했을 때 POSIX 스레드가 표준화 전이었고, fork()가 이식성이 높았다.

프로세스 구조

┌──────────┐
│ Postmaster│ ← 슈퍼바이저 (TCP 5432 대기)
└────┬─────┘
     │ fork()
     ├──→ Backend 1  (클라이언트 1 전담)
     ├──→ Backend 2  (클라이언트 2 전담)
     ├──→ Background Writer
     ├──→ WAL Writer
     ├──→ Checkpointer
     ├──→ Autovacuum Launcher
     ├──→ Logger
     └──→ Stats Collector
graph TB
    Postmaster["Postmaster<br/>(TCP 5432 대기)"]

    Postmaster -->|"fork()"| Backend1["Backend 1<br/>(클라이언트 1 전담)"]
    Postmaster -->|"fork()"| Backend2["Backend 2<br/>(클라이언트 2 전담)"]
    Postmaster -->|"fork()"| BGWriter["Background Writer"]
    Postmaster -->|"fork()"| WALWriter["WAL Writer"]
    Postmaster -->|"fork()"| Checkpointer["Checkpointer"]
    Postmaster -->|"fork()"| AutoVacuum["Autovacuum Launcher"]
    Postmaster -->|"fork()"| Logger["Logger"]
    Postmaster -->|"fork()"| StatsCollector["Stats Collector"]

    classDef mainProcess fill:#4A90E2,stroke:#2E5C8A,color:#fff
    classDef backendProcess fill:#7ED321,stroke:#5A9A18,color:#fff
    classDef bgProcess fill:#F5A623,stroke:#B87A1A,color:#fff

    class Postmaster mainProcess
    class Backend1,Backend2 backendProcess
    class BGWriter,WALWriter,Checkpointer,AutoVacuum,Logger,StatsCollector bgProcess

Postmaster

서버 시작 시 가장 먼저 생성되는 슈퍼바이저 프로세스다.

Backend 프로세스

클라이언트 연결과 1:1 매핑되는 프로세스다.

백그라운드 프로세스

Postmaster가 시작 시 fork하는 상주 프로세스들이다.

프로세스역할
Background Writer주기적으로 Shared Buffer의 dirty 페이지를 디스크에 기록
WAL WriterWAL 버퍼를 WAL 파일로 기록
Checkpointer체크포인트 시 모든 dirty 버퍼를 디스크에 flush
Autovacuum LauncherVACUUM이 필요한 테이블을 감지해 worker 기동 요청
Logger에러 메시지를 로그 파일에 기록
Stats Collector테이블/인덱스 통계 정보 수집

공유 메모리 (Shared Memory)

모든 프로세스가 공유하는 메모리 영역이다. 프로세스 격리의 이점을 유지하면서 필요한 데이터만 명시적으로 공유한다.

┌─────────┐  ┌─────────┐  ┌─────────┐
│Backend 1│  │Backend 2│  │Backend 3│
│(로컬 메모리)│  │(로컬 메모리)│  │(로컬 메모리)│
└────┬────┘  └────┬────┘  └────┬────┘
     │            │            │
     ▼            ▼            ▼
┌────────────────────────────────────┐
│         Shared Memory              │
│  ┌──────────────┐ ┌───────────┐   │
│  │Shared Buffers│ │WAL Buffers│   │
│  └──────────────┘ └───────────┘   │
│  ┌──────────────┐ ┌───────────┐   │
│  │  Lock Table  │ │ CLOG      │   │
│  └──────────────┘ └───────────┘   │
└────────────────────────────────────┘
graph LR
    Backend1["Backend 1<br/>(로컬 메모리)"]
    Backend2["Backend 2<br/>(로컬 메모리)"]
    Backend3["Backend 3<br/>(로컬 메모리)"]

    subgraph SharedMemory["공유 메모리 (Shared Memory)"]
        SharedBuffers["Shared Buffers<br/>(8KB 페이지 캐시)"]
        WALBuffers["WAL Buffers<br/>(변경사항 임시 저장)"]
        LockTable["Lock Table<br/>(잠금 정보 관리)"]
        CLOG["CLOG<br/>(커밋 상태 기록)"]
    end

    Backend1 -.->|접근| SharedBuffers
    Backend1 -.->|접근| WALBuffers
    Backend1 -.->|접근| LockTable
    Backend1 -.->|접근| CLOG

    Backend2 -.->|접근| SharedBuffers
    Backend2 -.->|접근| WALBuffers
    Backend2 -.->|접근| LockTable
    Backend2 -.->|접근| CLOG

    Backend3 -.->|접근| SharedBuffers
    Backend3 -.->|접근| WALBuffers
    Backend3 -.->|접근| LockTable
    Backend3 -.->|접근| CLOG

    classDef backend fill:#7ED321,stroke:#5A9A18,color:#fff
    classDef shared fill:#F5A623,stroke:#B87A1A,color:#fff

    class Backend1,Backend2,Backend3 backend
    class SharedBuffers,WALBuffers,LockTable,CLOG shared

WAL (Write-Ahead Logging)

"데이터를 변경하기 전에, 변경 내용을 먼저 로그에 기록한다"는 원칙이다.

COMMIT 시점:
  Backend → WAL Buffer → WAL 파일 (fsync, 순차 I/O)
                          ↑ 여기서 트랜잭션 확정

나중에 (Checkpoint):
  Shared Buffer → 데이터 파일 (랜덤 I/O)

COMMIT 시에는 WAL 순차 쓰기만 하면 되어 빠르고, 무거운 랜덤 I/O는 Checkpoint에서 한꺼번에 처리한다.

[!tip] WAL의 4가지 이점

  1. 장애 복구 — 크래시 후 WAL을 재생하여 데이터 복구
  2. 성능 향상 — 랜덤 I/O 대신 순차 I/O로 COMMIT
  3. 복제 — WAL을 다른 서버에 전송하면 Streaming Replication.md) 가능
  4. PITR — 특정 시점까지 WAL 재생으로 Point-in-Time Recovery

예시

-- Backend 프로세스 확인
SELECT pid, usename, application_name, state, query
FROM pg_stat_activity;

-- Shared Buffer 설정 확인
SHOW shared_buffers;     -- 기본 128MB
SHOW wal_buffers;        -- 기본 -1 (자동, shared_buffers의 1/32)
SHOW max_connections;    -- 기본 100

[!example] 실행 결과 pg_stat_activity에서 각 행이 하나의 Backend 프로세스에 해당한다. stateactive이면 쿼리 실행 중, idle이면 대기 상태다.

참고 자료

관련 노트