1.4 메모리 아키텍처
PostgreSQL의 메모리는 크게 공유 메모리(shared memory)와 각 backend 로컬 메모리로 나뉩니다. 공유 메모리는 postmaster가 기동 시 한 번 할당해 모든 프로세스가 같이 들여다보는 영역이고, 로컬 메모리는 backend·worker가 자기만 쓰는 작업 공간입니다. 그 위에 OS 페이지 캐시가 한 층 더 얹혀, 디스크 I/O를 흡수합니다.
메모리 계층
flowchart TD
subgraph OS["OS 메모리"]
PC["<b>page cache</b><br/>(커널이 관리)"]
subgraph PGShared["PostgreSQL Shared Memory<br/>postmaster 기동 시 1회 할당"]
SB["<b>shared_buffers</b><br/>데이터 페이지 캐시"]
WB["<b>WAL buffers</b><br/>WAL 임시 버퍼"]
CLOG["CLOG / SLRU 버퍼<br/>(트랜잭션 커밋 상태)"]
LOCK["lock tables<br/>proc array, predicate lock"]
RSLOT["replication slots"]
end
subgraph Backend["Backend 1 — Local Memory"]
WM["<b>work_mem</b><br/>정렬·해시 임시"]
TB["<b>temp_buffers</b><br/>세션 임시 테이블"]
CC["catalog cache,<br/>plan cache, relcache"]
end
subgraph Maint["Maintenance 작업"]
MWM["<b>maintenance_work_mem</b><br/>CREATE INDEX, VACUUM"]
end
end
classDef shared fill:#ede9fe,stroke:#6d28d9,color:#3b0764,stroke-width:2px
classDef local fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef maint fill:#fed7aa,stroke:#c2410c,color:#7c2d12
classDef os fill:#d1fae5,stroke:#047857,color:#064e3b
class SB,WB,CLOG,LOCK,RSLOT shared
class WM,TB,CC local
class MWM maint
class PC os
공유 메모리
postmaster는 기동 시 단일 mmap/shmem 영역을 할당합니다. 모든 backend·utility 프로세스가 이 영역을 공유합니다.
| 영역 | 파라미터 | 기본 | 역할 |
|---|---|---|---|
| 데이터 페이지 캐시 | shared_buffers | 128MB | 테이블·인덱스의 8KB 페이지를 메모리에 유지. 가장 큰 영역 |
| WAL 임시 버퍼 | wal_buffers | -1 (auto, shared_buffers의 1/32, 최대 16MB) | commit 전 WAL 레코드를 모아 두는 버퍼 |
| CLOG/SLRU 버퍼 | (내부) | 다수 | 트랜잭션 커밋 상태, multixact, subtrans, commit timestamp 등 SLRU 풀 |
| 락 테이블 | max_locks_per_transaction × max_connections | 64 × 100 | heavy-weight lock, predicate lock 메타데이터 |
| proc array | max_connections 외 | — | 활성 backend 목록·스냅샷 정보 |
| replication slots | max_replication_slots | 10 | 슬롯별 메타데이터 |
shared_buffers는 postmaster 기동 시점에 정해진 크기로 한 번 잡힌다. 운영 중에 늘리거나 줄일 수 없습니다. 변경하려면 postgresql.conf 수정 후 클러스터 재시작이 필요합니다.
shared_buffers 크기 결정
| RAM | 권장 shared_buffers |
|---|---|
| < 1GB | 더 보수적 ( |
| 1GB 이상 | 약 25% (PostgreSQL 공식 가이드) |
| 매우 큰 시스템 | 25~40% 정도. 그 이상은 OS page cache와의 이중 캐싱 손해가 커진다 |
PostgreSQL이 OS page cache에 적극적으로 의존하므로, shared_buffers를 무한정 키우는 게 답이 아닙니다. shared_buffers + OS cache 합쳐서 데이터셋이 들어가는 것이 이상적입니다.
huge pages
Linux 환경에서는 huge_pages = try(기본)·on으로 설정해 2MB(또는 1GB) huge pages를 쓸 수 있습니다.
| 설정 | 동작 |
|---|---|
try | huge page 풀이 부족하면 일반 4KB 페이지로 fallback (기본) |
on | huge page 확보 못 하면 기동 실패 |
off | 항상 4KB 페이지 |
이점: TLB miss 감소, 페이지 테이블 메모리 절감합니다. shared_buffers가 수 GB 이상이면 huge pages 권장합니다.
# 사전 작업 — huge page 풀 확보 (예: 2MB 페이지 1024개 = 2GB)
sysctl -w vm.nr_hugepages=1024
echo 'vm.nr_hugepages=1024' >> /etc/sysctl.d/40-postgresql.confmadvise 또는 never로 두는 게 운영 표준입니다.로컬 메모리
각 backend가 자기만 쓰는 메모리 영역. 동시 backend 수가 많을수록 이 합계가 커집니다.
| 파라미터 | 기본 | 역할 | 메모 |
|---|---|---|---|
work_mem | 4MB | 정렬·해시 조인·해시 집계 등 1개 노드당 사용 가능 메모리 | 한 쿼리에 정렬 노드가 여러 개면 노드 수만큼 곱해진다 |
hash_mem_multiplier | 2.0 | 해시 작업에 한해 work_mem × multiplier까지 허용 (PG 13+) | 해시 spilling 방지용 |
temp_buffers | 8MB | 세션의 임시 테이블 페이지 캐시 | 세션 첫 임시 테이블 사용 전에만 변경 가능 |
maintenance_work_mem | 64MB | CREATE INDEX, VACUUM, ALTER TABLE 등 유지보수 작업용 | 한 세션이 큰 값을 점유 가능. 보통 work_mem보다 크게 |
autovacuum_work_mem | -1 (= maintenance_work_mem) | autovacuum worker 1개당 메모리 상한 | autovacuum이 maintenance_work_mem을 다 차지하지 못하게 분리 가능 |
max_stack_depth | 2MB | backend 스택 깊이 상한 | 커널 ulimit -s보다 1MB 작게. 재귀 SQL·PL이 깊으면 늘림 |
work_mem의 함정
work_mem은 “쿼리당"이 아니라 노드(operation)당 적용됩니다. 한 쿼리에 hash join 2개 + sort 1개가 있으면 최대 work_mem × 3을 한 backend가 씁니다. 거기에 동시 backend 수와 parallel worker까지 곱해지면 —
peak memory ≈ work_mem × N(operations) × N(connections) × (1 + parallel_workers)work_mem = 64MB, max_connections = 200이고 각 쿼리에 정렬·해시 노드 3개가 있다면 이론적 최악은 64MB × 3 × 200 = 약 38GB. 운영 권장:
- 기본은 작게(4~16MB) 잡고
- 무거운 분석 쿼리만 세션 단위로
SET work_mem = '256MB'로 올린다 - PG 16+의
pg_stat_io·pg_stat_statements로 spilling 빈도 추적
maintenance_work_mem 결정
autovacuum이 작아도, 큰 테이블 인덱스 빌드·VACUUM 시에는 크게 가져갈수록 빠릅니다.
| 시나리오 | 추천 |
|---|---|
| 일반 OLTP | 256MB ~ 1GB |
| 대용량 인덱스 빌드 잦음 | 1~4GB |
| autovacuum이 무거운 시스템 | autovacuum_work_mem을 작게(256MB) 분리하고 maintenance_work_mem은 크게 |
effective_cache_size — 할당이 아닌 힌트
| 파라미터 | 기본 | 의미 |
|---|---|---|
effective_cache_size | 4GB | 옵티마이저가 가정하는 shared_buffers + OS page cache 합산 크기 |
이 값은 메모리를 실제로 할당하지 않는다. 단지 옵티마이저가 인덱스 스캔 vs 시퀀셜 스캔의 비용을 추정할 때 참고하는 힌트입니다. 너무 작으면 옵티마이저가 디스크 캐시가 작다고 가정해 인덱스 스캔 비용을 비싸게 봅니다.
권장값: 호스트 RAM의 50~75%.
OS 페이지 캐시 — 보이지 않는 두 번째 캐시
PostgreSQL은 디스크 I/O를 OS 표준 read()/write()로 합니다. 따라서 모든 페이지가 일단 OS page cache를 거칩니다. 같은 페이지가 shared_buffers와 OS cache에 동시에 있는 이중 캐시 구조다.
flowchart LR
Disk["디스크<br/>(PGDATA, pg_wal)"]
PC["OS page cache"]
SB["shared_buffers"]
BE["backend"]
Disk <--> PC
PC <--> SB
SB <--> BE
classDef d fill:#fef3c7,stroke:#b45309,color:#78350f
classDef p fill:#d1fae5,stroke:#047857,color:#064e3b
classDef s fill:#ede9fe,stroke:#6d28d9,color:#3b0764
classDef b fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
class Disk d
class PC p
class SB s
class BE b
이중 캐시의 의미:
shared_buffers를 너무 키우면 같은 페이지가 양쪽에 캐싱돼 메모리 낭비- 너무 작으면 hot page를 OS cache에서 재로딩하는 비용
- 일반적으로 25% 권장합니다. 매우 read-heavy + 큰 buffer pool 검증된 워크로드만 40%+
Direct I/O 옵션 (io_uring, io_method)
PostgreSQL 18에서 비동기 I/O(AIO)가 본격 도입됐습니다. io_method = io_uring(Linux) 또는 worker로 설정하면 backend가 별도 worker를 통해 비동기로 페이지를 prefetch합니다. 이중 캐시 자체는 그대로지만 레이턴시가 개선됩니다.
메모리 압박 진단
| 증상 | 의심 영역 | 진단 |
|---|---|---|
temp_file 빈도 높음 (pg_stat_database.temp_files) | work_mem 부족 — 정렬·해시가 디스크로 spill | 세션별 work_mem 증액 |
pg_stat_io에서 evicted 많음 (PG 16+) | shared_buffers 부족 — hot page가 자주 밀려남 | shared_buffers 증액 |
| autovacuum이 너무 느림 | maintenance_work_mem 부족 | maintenance·autovacuum_work_mem 증액 |
| OOM Killer가 postgres 죽임 | work_mem × connections가 OS RAM 초과 | work_mem 감소 또는 max_connections 감소 |
| 옵티마이저가 인덱스 안 쓰는 경향 | effective_cache_size 너무 작음 | RAM의 50~75%로 |
SET LOCAL로 분리하는 패턴을 권장합니다.정리
- 공유 메모리 =
shared_buffers외에 WAL buffer·SLRU·lock table 등. postmaster가 기동 시 한 번 할당 - 로컬 메모리 = backend별로
work_mem·temp_buffers·플랜 캐시 등을 자체 보유 work_mem은 노드당 적용 —work_mem × 노드 × 연결 × parallel로 최악 케이스 추정effective_cache_size는 옵티마이저 힌트, 실제 할당 아님- OS page cache가 두 번째 캐시 —
shared_buffers를 25% 정도로 두고 OS와 분담 - huge pages 권장, THP는
madvise/never
다음 절(1.5)에서는 이 메모리들이 실제로 가리키는 디스크 구조 — segments·pages·tuples·free space map — 봅니다.