PostgreSQL 운영 아키텍처: process model, shared buffers, WAL
PostgreSQL이 멀티프로세스인 이유
PostgreSQL은 스레드 모델이 아닌 멀티프로세스 모델을 선택했다. 클라이언트 연결 하나마다 별도의 OS 프로세스(backend)가 생성된다. 이 설계는 한 프로세스가 크래시하더라도 다른 연결에 영향을 주지 않는 강한 격리성을 제공한다. 대신 연결 오버헤드가 크기 때문에, 프로덕션 환경에서는 반드시 PgBouncer 같은 커넥션 풀러를 앞에 두어야 한다.
프로세스 모델 전체 그림
Postmaster: 모든 것의 시작점
Postmaster는 PostgreSQL의 최상위 프로세스다. 주요 역할은 세 가지다.
- 공유 메모리 할당: 서버 시작 시
shared_buffers, WAL 버퍼, CLOG 버퍼, Lock Table 등을 포함하는 거대한 공유 메모리 세그먼트를 mmap으로 할당한다. - 연결 수락 및 fork: 새 클라이언트 연결이 들어오면
fork()로 backend 프로세스를 생성한다. fork 비용이 크므로 프로덕션에서는 PgBouncer가 필수다. - 백그라운드 프로세스 감시: checkpointer, bgwriter, walwriter, autovacuum launcher 등이 죽으면 재시작한다.
Postmaster 자체가 죽으면 전체 클러스터가 shutdown된다.
# 실행 중인 PostgreSQL 프로세스 확인
ps aux | grep postgres
# 출력 예:
# postgres 1234 0.0 ... /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main
# postgres 1235 0.0 ... postgres: checkpointer
# postgres 1236 0.0 ... postgres: background writer
# postgres 1237 0.0 ... postgres: walwriter
# postgres 1238 0.0 ... postgres: autovacuum launcher
# postgres 1239 0.0 ... postgres: stats collector ← PG14 이하에만 존재
# postgres 1240 0.0 ... postgres: logical replication launcher
# postgres 2001 0.0 ... postgres: app user mydb 10.0.0.5(54321) idle백그라운드 프로세스 상세
checkpointer
모든 더티 버퍼(shared_buffers에서 수정됐으나 아직 디스크에 반영 안 된 페이지)를 디스크로 flush하고 WAL에 checkpoint record를 기록한다. 체크포인트 이후에는 이전 WAL 파일이 더 이상 복구에 필요 없으므로 재활용하거나 삭제할 수 있다.
체크포인트가 트리거되는 조건:
checkpoint_timeout(기본 5분) 경과- WAL 증가량이
max_wal_size(기본 1GB) 도달 - 수동
CHECKPOINT명령
-- 체크포인트 현황 조회 (PG16+에서는 pg_stat_checkpointer로 분리됨)
SELECT
checkpoints_timed, -- 주기(timeout) 트리거 체크포인트
checkpoints_req, -- 용량(max_wal_size) 트리거 체크포인트
checkpoint_write_time, -- ms
checkpoint_sync_time, -- ms (fsync 시간)
buffers_checkpoint, -- 체크포인트에서 쓴 버퍼 수
buffers_clean, -- bgwriter가 쓴 버퍼 수
buffers_backend, -- backend가 직접 쓴 버퍼 수 ← 이 값이 크면 bgwriter 부족
maxwritten_clean -- bgwriter가 한계에 걸린 횟수
FROM pg_stat_bgwriter;checkpoints_req가 checkpoints_timed보다 많으면 WAL이 너무 빠르게 생성되고 있다는 신호다. max_wal_size를 늘려 체크포인트 빈도를 줄인다.
buffers_backend가 크면 bgwriter가 충분히 clean buffer를 공급하지 못하고 있다. bgwriter_lru_maxpages나 bgwriter_delay를 조정한다.
bgwriter (Background Writer)
bgwriter는 체크포인트 사이에 더티 페이지를 미리 디스크로 내려보낸다. 체크포인트 때 한꺼번에 대량의 I/O가 몰리는 것을 방지하고, backend가 페이지 eviction 시 직접 I/O를 하지 않아도 되게 한다.
핵심 파라미터:
| 파라미터 | 기본값 | 역할 |
|---|---|---|
bgwriter_delay | 200ms | 라운드 간격 |
bgwriter_lru_maxpages | 100 | 라운드당 최대 flush 페이지 |
bgwriter_lru_multiplier | 2.0 | 예상 수요의 몇 배를 clean해 둘지 |
walwriter
WAL 버퍼(shared memory)를 실제 WAL 파일(pg_wal/)로 주기적으로 fsync한다. backend가 COMMIT 시점에 직접 flush하지 않아도 될 경우 walwriter가 대신 처리한다.
wal_writer_delay(기본 200ms)마다 깨어나 WAL 버퍼를 flush한다. 동기 복제 환경에서는 COMMIT이 walwriter를 즉시 깨운다.
autovacuum launcher / worker
PostgreSQL은 MVCC 구조상 삭제/업데이트된 행이 즉시 제거되지 않고 "dead tuple"로 남는다. autovacuum이 이를 정리하지 않으면 테이블이 무한히 부풀어오르고(bloat), xid wraparound라는 치명적 장애로 이어진다.
- launcher:
autovacuum_naptime(기본 1분)마다 깨어나pg_stat_all_tables를 확인해 dead tuple 임계값 초과 테이블을 찾는다. - worker: 실제 vacuum/analyze 수행. 최대
autovacuum_max_workers(기본 3)개 동시 실행.
-- autovacuum 대상이 될 가능성이 높은 테이블 조회
SELECT
schemaname, relname,
n_dead_tup,
n_live_tup,
ROUND(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS dead_ratio_pct,
last_autovacuum,
last_autoanalyze
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC
LIMIT 20;walsender / walreceiver
스트리밍 복제를 담당한다. Primary에서 walsender가 WAL 스트림을 replica의 walreceiver로 전송한다. 논리 복제(logical replication) 시에도 walsender가 사용된다.
공유 메모리 구성요소
Shared Buffers
PostgreSQL의 데이터 페이지 캐시다. 모든 backend와 백그라운드 프로세스가 이 공간을 공유한다.
- 기본값: 128MB (너무 작음)
- 권장값: 서버 RAM의 25~35%
- 상한: 40% 이상은 OS 페이지 캐시 공간을 빼앗아 역효과가 날 수 있음
-- 현재 shared_buffers 확인
SHOW shared_buffers;
-- pg_buffercache로 버퍼 사용 현황 조회 (확장 필요)
CREATE EXTENSION IF NOT EXISTS pg_buffercache;
SELECT
c.relname,
COUNT(*) AS buffers,
ROUND(COUNT(*) * 8192 / 1024.0 / 1024.0, 1) AS mb
FROM pg_buffercache b
JOIN pg_class c ON b.relfilenode = pg_relation_filenode(c.oid)
WHERE b.isdirty = false
GROUP BY c.relname
ORDER BY buffers DESC
LIMIT 20;WAL Buffers
WAL 레코드를 WAL 파일에 쓰기 전에 임시 보관하는 버퍼다.
- 기본값:
-1(shared_buffers의 1/32, 최대 16MB) - 권장값: 대부분
-1그대로 사용하거나 명시적으로16MB설정
SHOW wal_buffers;CLOG (Commit Log) Buffer
각 트랜잭션의 커밋/롤백/진행 중 상태를 비트맵으로 저장한다. MVCC visibility check 시 사용된다. 별도 파라미터 없이 자동 관리된다.
Lock Table
모든 heavyweight lock 정보를 저장한다. max_locks_per_transaction(기본 64) × max_connections로 크기가 결정된다. 매우 많은 객체를 동시에 잠그는 배치 작업은 이 값을 높여야 한다.
WAL 쓰기 경로
UPDATE/INSERT
WAL Buffer에 기록
페이지 dirty 표시
walwriter / backend가
pg_wal/ 에 fsync
내구성 보장 완료
trickle flush
데이터 파일 갱신
checkpointer
checkpoint record → WAL
복구 기준점 이동
핵심 내구성 원리: COMMIT 시 WAL이 디스크에 fsync되면 트랜잭션은 내구적이다. 데이터 파일은 나중에 써도 된다. 크래시 후 복구 시 WAL을 재생(replay)해 데이터 파일을 최신 상태로 맞춘다.
synchronous_commit = off로 설정하면 COMMIT 후 WAL flush를 비동기로 처리해 지연을 줄일 수 있다. 하지만 크래시 시 최근 커밋 일부가 유실될 수 있다. 데이터 손실이 허용 안 되는 테이블에는 절대 사용하면 안 된다.
핵심 파라미터 튜닝 가이드
-- postgresql.conf 주요 파라미터 현재 값 조회
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name IN (
'shared_buffers', 'wal_buffers',
'checkpoint_timeout', 'max_wal_size', 'min_wal_size',
'checkpoint_completion_target',
'bgwriter_delay', 'bgwriter_lru_maxpages',
'max_connections', 'work_mem', 'maintenance_work_mem'
)
ORDER BY name;| 파라미터 | 기본값 | 권장값 | 설명 |
|---|---|---|---|
shared_buffers | 128MB | RAM × 25~35% | 데이터 페이지 캐시 |
wal_buffers | -1 (auto) | 16MB 또는 -1 | WAL 메모리 버퍼 |
checkpoint_timeout | 5min | 10~15min | 주기 체크포인트 간격 |
max_wal_size | 1GB | 4~8GB (쓰기 많은 서버) | 체크포인트 트리거 WAL 크기 |
min_wal_size | 80MB | 512MB~1GB | WAL 파일 재활용 최소 크기 |
checkpoint_completion_target | 0.9 | 0.9 | 체크포인트 I/O 분산 비율 |
bgwriter_delay | 200ms | 50~200ms | bgwriter 라운드 간격 |
bgwriter_lru_maxpages | 100 | 200~500 (쓰기 많은 서버) | 라운드당 최대 flush 페이지 |
checkpoint_completion_target = 0.9는 체크포인트 I/O를 다음 체크포인트까지 남은 시간의 90% 동안 분산하겠다는 의미다. I/O 스파이크를 방지하는 중요한 파라미터다.
통계 뷰와 모니터링
-- checkpointer/bgwriter 활동 현황
SELECT
checkpoints_timed,
checkpoints_req,
ROUND(checkpoint_write_time / 1000.0, 1) AS write_sec,
ROUND(checkpoint_sync_time / 1000.0, 1) AS sync_sec,
buffers_checkpoint,
buffers_clean,
buffers_backend, -- 클수록 bgwriter 부족 신호
maxwritten_clean, -- bgwriter 한계 도달 횟수
buffers_alloc -- 새로 할당된 버퍼 수
FROM pg_stat_bgwriter;
-- WAL 활동 현황 (PG14+)
SELECT
wal_records,
wal_fpi, -- full page image 수
wal_bytes,
wal_buffers_full, -- WAL 버퍼 꽉 찬 횟수 (0이어야 좋음)
wal_write,
wal_sync,
wal_write_time,
wal_sync_time
FROM pg_stat_wal;
-- 현재 백그라운드 프로세스 목록
SELECT pid, backend_type, state, query
FROM pg_stat_activity
WHERE backend_type != 'client backend'
ORDER BY backend_type;wal_buffers_full이 증가하면 WAL 버퍼가 너무 작은 것이다. wal_buffers를 16MB 이상으로 늘린다.
PG14 → PG15 통계 수집 방식 변화
PG14까지는 별도의 stats collector 프로세스가 pg_stat_activity 등의 통계를 수집했다. PG15부터는 통계가 공유 메모리에 직접 기록되어 stats collector 프로세스가 사라졌다. 덕분에 프로세스 간 IPC 지연이 없어지고 통계의 실시간성이 향상됐다.
References
- PostgreSQL Documentation, "Chapter 19: Server Configuration": https://www.postgresql.org/docs/current/runtime-config.html
- PostgreSQL Documentation, "pg_stat_bgwriter": https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-BGWRITER-VIEW
- PostgreSQL Documentation, "pg_stat_wal": https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-WAL-VIEW
- Highgo Software, "An Overview of PostgreSQL Backend Architecture": https://www.highgo.ca/2020/06/18/an-overview-of-postgresql-backend-architecture/
- EDB Blog, "Tuning shared_buffers and wal_buffers": https://www.enterprisedb.com/blog/tuning-sharedbuffers-and-walbuffers
- AWS Blog, "Determining the optimal value for shared_buffers": https://aws.amazon.com/blogs/database/determining-the-optimal-value-for-shared_buffers-using-the-pg_buffercache-extension-in-postgresql/
- postgresql.fastware.com, "Let's get back to basics - PostgreSQL memory components": https://www.postgresql.fastware.com/blog/lets-get-back-to-basics-postgresql-memory-components
- postgresql.fastware.com, "Understanding the importance of shared_buffers, work_mem, and wal_buffers": https://www.postgresql.fastware.com/pzone/2024-06-understanding-shared-buffers-work-mem-and-wal-buffers-in-postgresql
- Mydbops, "PostgreSQL Performance Tuning Best Practices 2025": https://www.mydbops.com/blog/postgresql-parameter-tuning-best-practices