필사 모드: PostgreSQL이 다시 1위가 된 이유 심화 가이드 — pgvector, HNSW, JSONB, MVCC, Supabase, Neon, TimescaleDB, AI 시대의 DB 전략 (2025)
한국어> **TL;DR** — PostgreSQL은 2024년 StackOverflow 개발자 설문에서 **MySQL을 제치고 처음으로 1위**에 올랐다(49%). 동시에 **pgvector** 확장이 Pinecone, Weaviate, Qdrant 같은 전용 벡터 DB를 위협하고, **JSONB + GIN index**가 MongoDB의 영역을 잠식하고, **Supabase · Neon · PlanetScale · CockroachDB**가 Firebase를 대체하고, **TimescaleDB · PostGIS · Citus**가 전용 솔루션을 통합한다. 이 글은 PostgreSQL이 '만능 DB'로 돌아온 배경을 **MVCC의 우아함**, **WAL과 PITR**, **HNSW 알고리즘**, **Logical Replication**, **PostgreSQL 17 신기능**, 그리고 **AI 시대의 RAG 아키텍처**까지 — 원리와 실전을 모두 추적한다.
PostgreSQL이 다시 1위가 된 이유
1996년 UC Berkeley의 Ingres(1974) 계보를 잇는 **Postgres**가 SQL을 추가해 **PostgreSQL**로 발표된 지 거의 30년. 오랜 시간 MySQL의 그늘에 있던 이 DB가 2023-2024년 사이 '개발자들이 가장 사랑하는 DB'로 올라섰다. StackOverflow 2024 개발자 설문:
- **PostgreSQL 49%** (1위, 전년 대비 +4%)
- **MySQL 40.5%** (2위, -3%)
- **SQLite 33%** (3위, +2%)
- **MongoDB 25.5%** (4위, -3%)
- **Redis 20%** (5위)
**Most Admired Database**(가장 사랑받는 DB)에서 PostgreSQL은 **74%** — 2위 Redis(68%)와 격차.
숫자만의 문제가 아니다. 클라우드 서비스가 PostgreSQL을 '기본'으로 삼는다:
- **AWS Aurora PostgreSQL** — 재작성된 스토리지 엔진으로 MySQL 호환과 동등하게
- **Google Cloud SQL, AlloyDB** — PostgreSQL 중심 재편
- **Microsoft Azure Database for PostgreSQL** — CosmosDB와 함께 주력
- **Supabase** — "Firebase의 오픈소스 대안" — PostgreSQL + Auth + Storage + Edge Functions
- **Neon** — Serverless PostgreSQL, Branch 기반 DB
- **Crunchy Bridge, Xata, Turso** — PostgreSQL 특화 스타트업
- **PlanetScale** — MySQL 중심이었으나 2024년 PostgreSQL 출시
**왜 이 시점에?** 4가지 이유:
1. **JSONB**로 NoSQL 수요 흡수 (MongoDB의 주 영역 침식)
2. **pgvector**로 AI/RAG 수요 흡수 (Pinecone, Weaviate 영역 침식)
3. **Logical Replication, Partitioning, Parallel Query** 성숙 (확장성 문제 해결)
4. **확장 생태계** — TimescaleDB(시계열), PostGIS(지리), Citus(분산), pg_cron(스케줄러)로 전용 DB 필요성 감소
PostgreSQL의 건축학 — 프로세스 모델
PostgreSQL은 **MySQL/Oracle과 다른 프로세스 기반**(process-per-connection) 아키텍처를 쓴다. 각 연결마다 OS 프로세스를 fork.
postmaster (main process, port 5432 listener)
│
├─ backend process #1 (client connection)
├─ backend process #2
├─ backend process #3
...
├─ autovacuum launcher → autovacuum workers
├─ background writer (dirty page 비동기 쓰기)
├─ checkpointer (checkpoint 관리)
├─ WAL writer (Write-Ahead Log)
├─ logical replication launcher → workers
├─ archiver (WAL 파일 보관)
└─ stats collector
**이 모델의 장단점**:
- **장점**: 프로세스 격리로 한 쿼리가 전체 DB를 다운시키지 않음. 안정성 높음.
- **단점**: 연결당 비용 크고 (약 10MB), 동시 연결 수 한계 (수백 개 수준).
- **해결**: **PgBouncer**, **Pgpool-II** 커넥션 풀러, 또는 PostgreSQL 17의 내장 풀링 개선.
MVCC — Multi-Version Concurrency Control의 우아함
PostgreSQL의 철학적 심장은 **MVCC**다. '읽기가 쓰기를 막지 않고, 쓰기가 읽기를 막지 않는다'는 원칙을 **버전 추적**으로 구현.
핵심 원리
모든 튜플(row)은 숨겨진 시스템 컬럼을 갖는다:
SELECT ctid, xmin, xmax, cmin, cmax, * FROM users;
- `xmin` — 이 버전을 생성한 트랜잭션 ID
- `xmax` — 이 버전을 삭제/업데이트한 트랜잭션 ID (0이면 현재 버전)
- `ctid` — 물리적 위치 (page, offset)
**UPDATE는 덮어쓰기가 아니라 새 튜플 삽입 + 이전 튜플 xmax 설정**:
초기:
(xmin=100, xmax=0, id=1, name='Alice')
트랜잭션 200이 UPDATE name='Bob':
(xmin=100, xmax=200, id=1, name='Alice') ← dead tuple
(xmin=200, xmax=0, id=1, name='Bob') ← live tuple
**트랜잭션 150(UPDATE 이전 시작)이 SELECT 하면**: `xmin=100` 튜플을 본다. `xmin=200` 튜플은 아직 커밋 안 된 것으로 보임.
MVCC의 이득
1. **Consistent Read** — 트랜잭션 내 SELECT는 시작 시점의 스냅샷을 봄 (Repeatable Read 격리 기본 제공)
2. **Non-blocking Reads** — SELECT가 UPDATE/DELETE를 기다리지 않음
3. **Snapshot Isolation** — `BEGIN ISOLATION LEVEL REPEATABLE READ`로 일관된 세계관
MVCC의 비용 — VACUUM
dead tuple이 쌓이면 디스크가 늘어나고 쿼리가 느려진다. **VACUUM**이 주기적으로 정리:
VACUUM users; -- dead tuple 회수
VACUUM FULL users; -- 테이블 rewrite (lock 필요, 프로덕션에서 금지)
VACUUM ANALYZE users; -- vacuum + 통계 갱신
REINDEX TABLE users; -- index rebuild
**autovacuum**이 기본 활성화되어 자동 실행. 설정 튜닝:
postgresql.conf
autovacuum = on
autovacuum_vacuum_scale_factor = 0.1 -- 10% dead면 vacuum
autovacuum_vacuum_cost_limit = 2000 -- I/O 예산
autovacuum_naptime = 1min
대규모 테이블은 **PARTITIONING**으로 VACUUM 부담 분산.
Transaction ID Wraparound — 32비트 ID의 함정
XID(Transaction ID)는 32비트. **40억 트랜잭션**마다 wraparound. PostgreSQL은 'freeze' 메커니즘으로 과거 튜플의 xmin을 `FrozenXID`로 표시해 해결. autovacuum이 관리하지만, 큰 DB는 `autovacuum_freeze_max_age` 조정 필요.
Write-Ahead Logging (WAL) — ACID의 비밀
PostgreSQL의 모든 변경은 먼저 **WAL**에 기록된 뒤 실제 데이터 파일에 반영된다.
[Client] --> UPDATE --> [Backend Process]
│
├─> WAL Buffer 작성
├─> Shared Buffer 수정 (dirty page)
└─> COMMIT 시 WAL Flush to disk (fsync)
[Background]
checkpointer --> dirty page를 data files에 flush
WAL writer --> WAL buffer를 디스크에 flush
archiver --> WAL segment를 archive 위치로 복사
**WAL의 4가지 역할**:
1. **Durability** — COMMIT 후 크래시해도 WAL 재생으로 복구
2. **Replication** — 물리/논리 복제의 데이터 소스
3. **PITR** — Point-in-Time Recovery (특정 시점 복구)
4. **Streaming Replication** — Standby 서버로 실시간 스트리밍
PITR — 30분 전 상태로 되돌리기
1. Base Backup
pg_basebackup -D /backup/base -F tar -z -P
2. WAL 아카이빙 유지 (postgresql.conf)
archive_mode = on
archive_command = 'cp %p /archive/%f'
3. 복구 — recovery.conf (PostgreSQL 12+ postgresql.conf)
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2025-01-15 14:30:00 KST'
**도구**: `pgBackRest`, `Barman`, `WAL-E/WAL-G` (AWS S3, GCS).
Logical Replication — PostgreSQL 10+의 결정적 변화
PostgreSQL의 복제는 전통적으로 **Physical Replication** (바이너리 WAL 스트리밍)이었다. 전체 클러스터를 복제. 2017년 **Logical Replication**이 추가되며:
- **테이블 단위** 복제 가능
- **다른 PostgreSQL 버전 간** 복제 (메이저 업그레이드 zero-downtime)
- **선택적 컬럼** 복제 (PostgreSQL 15+)
- **CDC (Change Data Capture)** 네이티브 지원 → Kafka, Debezium 통합
-- Publisher (source)
CREATE PUBLICATION my_pub FOR TABLE users, orders;
ALTER TABLE users REPLICA IDENTITY FULL;
-- Subscriber (destination)
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=source dbname=prod user=repl'
PUBLICATION my_pub;
**PostgreSQL 16의 개선**:
- 병렬 적용 (parallel apply)
- Standby 서버에서 logical replication 가능
- bidirectional replication 기반 마련
**PostgreSQL 17의 도약** (2024.09 릴리스):
- `pg_createsubscriber` — standby를 logical subscriber로 변환
- Incremental Backup (`pg_basebackup --incremental`)
- Logical Replication 슬롯 장애 복구 개선
- `MERGE` 구문의 `RETURNING` 지원
JSONB — MongoDB를 위협하는 주 무기
PostgreSQL 9.2(2012)의 `json`, 9.4(2014)의 `jsonb`. jsonb는 **바이너리 저장 + 인덱스 지원**으로 MongoDB급 성능을 제공.
JSONB 기본
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT NOW(),
payload JSONB NOT NULL
);
INSERT INTO events (payload) VALUES
('{"user_id": 42, "action": "login", "tags": ["web", "mobile"]}'),
('{"user_id": 43, "action": "purchase", "amount": 99.99}');
-- 경로 추출
SELECT payload->>'user_id' FROM events; -- text
SELECT payload->'tags' FROM events; -- jsonb
SELECT payload#>>'{user,profile,email}' FROM events; -- 깊은 경로
-- 조건 검색
SELECT * FROM events WHERE payload->>'action' = 'login';
SELECT * FROM events WHERE payload @> '{"user_id": 42}'; -- contains
SELECT * FROM events WHERE payload ? 'amount'; -- key exists
GIN Index로 초고속 JSONB 검색
-- 전체 jsonb 인덱싱
CREATE INDEX idx_payload ON events USING GIN (payload);
-- 경로별 jsonb_path_ops (더 작고 빠름)
CREATE INDEX idx_payload_ops ON events USING GIN (payload jsonb_path_ops);
-- 특정 키만
CREATE INDEX idx_user_id ON events ((payload->>'user_id'));
JSONB vs MongoDB — 실전 비교
| 기능 | PostgreSQL JSONB | MongoDB |
|---|---|---|
| Schema | Optional (유연) | Schemaless |
| Transaction | ACID (기본) | ACID (4.0+) |
| Join | SQL JOIN 완벽 지원 | `$lookup` (제한적) |
| Index | GIN, B-tree, BRIN | B-tree, compound |
| Aggregation | SQL + window | aggregation pipeline |
| 관계형 데이터 | 강력 | 약함 |
| JSON-only 워크로드 | 충분 | 최적화 |
"**document + relational** 둘 다 필요한 현대 앱"에서 PostgreSQL이 압도적.
pgvector — AI 시대의 핵무기
2021년 Andrew Kane이 만든 **pgvector** 확장. 2023년 OpenAI가 공식 추천하면서 폭발적 성장. 2024년 기준 GitHub Star 11k+.
설치 + 기본
CREATE EXTENSION vector;
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536) -- OpenAI ada-002 차원
);
INSERT INTO documents (content, embedding) VALUES
('PostgreSQL is awesome', '[0.1, 0.2, ...]'),
('MongoDB is document-oriented', '[0.3, 0.1, ...]');
-- 최근접 이웃 검색 (Euclidean)
SELECT content, embedding <-> '[0.15, 0.18, ...]' AS distance
FROM documents
ORDER BY distance
LIMIT 5;
-- Cosine similarity
SELECT content, 1 - (embedding <=> query_vec) AS similarity
FROM documents
ORDER BY embedding <=> query_vec
LIMIT 5;
-- Inner product (dot product)
SELECT content, embedding <#> query_vec AS negative_ip
FROM documents
ORDER BY embedding <#> query_vec
LIMIT 5;
**연산자 3가지**:
- `<->` — L2 거리 (Euclidean)
- `<#>` — 내적 (negative inner product)
- `<=>` — 코사인 거리 (1 - cosine similarity)
IVFFlat vs HNSW
전체 스캔은 수백만 벡터에서 느리다. **Approximate Nearest Neighbor (ANN)** 인덱스 필수.
**IVFFlat** (Inverted File with Flat lists):
-- 데이터 삽입 후 (통계 필요)
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100); -- sqrt(row 수)가 일반적
-- 검색 시 탐색할 리스트 수
SET ivfflat.probes = 10;
- 원리: k-means로 벡터를 클러스터링, 쿼리 시 가까운 클러스터만 탐색
- 장점: 빌드 빠름, 메모리 적음
- 단점: recall 정확도가 HNSW보다 낮음
**HNSW** (Hierarchical Navigable Small World) — pgvector 0.5.0(2023)부터 추가:
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
SET hnsw.ef_search = 100; -- 검색 품질 vs 속도 조절
- 원리: **다층 그래프 구조**. 상위 레이어는 sparse, 하위는 dense. 상위에서 시작해 greedy하게 내려가며 이웃 탐색.
- 장점: **recall 95-99% + 매우 빠름** — pgvector 기본 추천
- 단점: 빌드 시간 + 메모리 많이 필요
**성능 비교** (1M 벡터, 1536-dim):
| 방법 | Build Time | Index Size | QPS | Recall |
|---|---|---|---|---|
| Seq Scan | - | - | ~10 | 100% |
| IVFFlat | 10분 | 6GB | ~500 | 95% |
| HNSW | 30분 | 8GB | ~3000 | 98% |
pgvector vs 전용 벡터 DB
| DB | 타입 | 1M 벡터 QPS | 장점 | 단점 |
|---|---|---|---|---|
| **pgvector** | PostgreSQL 확장 | 3,000 | SQL + metadata join | 수천만 이상에서 부하 |
| **Pinecone** | SaaS 전용 | 15,000+ | 관리 필요 없음 | 비싸고 lock-in |
| **Weaviate** | 오픈소스 | 10,000 | GraphQL, 하이브리드 | 별도 운영 부담 |
| **Qdrant** | 오픈소스 (Rust) | 20,000 | 필터 성능, 빠름 | PostgreSQL과 별도 |
| **Milvus** | 오픈소스 | 30,000+ | 스케일 최강 | 복잡도 높음 |
**선택 가이드**:
- **5천만 벡터 이하 + SQL 메타데이터** → pgvector ✅
- **억 단위 벡터 + 초고성능** → Qdrant, Milvus
- **관리 싫음, 예산 OK** → Pinecone
pgvector의 최신 업그레이드 (0.7 → 0.8, 2024)
- **Halfvec** — float16으로 메모리 반감
- **Sparse Vector** — BM25 같은 sparse embedding (hybrid search용)
- **Binary Quantization** — bit-packing으로 32배 압축
- **Iterative Index Scan** — 필터링된 결과에서도 recall 유지
-- Halfvec 사용
ALTER TABLE documents
ADD COLUMN embedding_half halfvec(1536);
UPDATE documents SET embedding_half = embedding::halfvec(1536);
CREATE INDEX ON documents USING hnsw (embedding_half halfvec_cosine_ops);
RAG (Retrieval-Augmented Generation) 실전 파이프라인
pgvector가 AI 시대의 핵심이 된 이유: **RAG**. LLM이 사내 지식을 알려면 벡터 검색이 필수.
[사용자 질문]
↓
[Embedding API] (OpenAI, Cohere, BGE)
↓
[pgvector HNSW 검색] ← [사전 임베딩된 문서 DB]
↓
[Top-K 문서 선택]
↓
[Prompt 재구성] "다음 문서를 참고해서 답해라: ..."
↓
[LLM 호출] (GPT-4, Claude, Llama)
↓
[응답]
**SQL 구현 예시**:
-- 문서 임베딩 테이블
CREATE TABLE docs (
id BIGSERIAL PRIMARY KEY,
title TEXT,
content TEXT,
chunk_idx INT,
embedding vector(1536),
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON docs USING GIN (metadata);
CREATE INDEX ON docs USING GIN (to_tsvector('simple', content));
-- Hybrid search: 벡터 + BM25 + 메타데이터
WITH vector_search AS (
SELECT id, 1 - (embedding <=> $1) AS vec_score
FROM docs
WHERE metadata @> '{"lang": "ko"}'
ORDER BY embedding <=> $1
LIMIT 20
),
fts_search AS (
SELECT id, ts_rank(to_tsvector('simple', content), to_tsquery($2)) AS fts_score
FROM docs
WHERE to_tsvector('simple', content) @@ to_tsquery($2)
LIMIT 20
)
SELECT d.*,
COALESCE(v.vec_score, 0) * 0.7 + COALESCE(f.fts_score, 0) * 0.3 AS score
FROM docs d
LEFT JOIN vector_search v ON d.id = v.id
LEFT JOIN fts_search f ON d.id = f.id
WHERE v.id IS NOT NULL OR f.id IS NOT NULL
ORDER BY score DESC
LIMIT 5;
이게 **hybrid search**. 벡터만 쓰면 keyword 매칭이 약하고, BM25만 쓰면 의미가 약하다. 둘을 결합해 recall과 precision 모두 높임.
PostgreSQL 17의 결정적 기능들 (2024.09)
- **`pg_createsubscriber`** — standby 서버를 logical subscriber로 변환 (메이저 버전 업그레이드 zero-downtime)
- **Incremental Backup** — `pg_basebackup --incremental` + `pg_combinebackup`
- **VACUUM 성능** — 메모리 관리 개선으로 대형 테이블 최대 20배 빠름
- **JSON_TABLE** — SQL/JSON 표준 함수 (복잡한 JSONB를 테이블로 변환)
- **MERGE RETURNING** — upsert 후 결과 반환
- **SIMD (AVX-512) 최적화** — bytea 처리 가속
- **Login Event Trigger** — 로그인 시점 트리거 가능
-- JSON_TABLE 예시
SELECT t.* FROM events e,
JSON_TABLE(e.payload, '$' COLUMNS (
user_id INT PATH '$.user_id',
action TEXT PATH '$.action',
amount NUMERIC PATH '$.amount' DEFAULT 0 ON EMPTY
)) t;
Supabase, Neon, PlanetScale — 2025 PostgreSQL 클라우드
Supabase
- **"Firebase 대안"** — 2020년 창업, 2024년 YC W20 최대 성공작 중 하나
- 포함: PostgreSQL + PostgREST(auto REST) + GoTrue(auth) + Storage + Realtime + Edge Functions
- pgvector 기본 내장, AI 기능 1등급 지원
- 무료 플랜: 500MB + 2 projects
- 오픈소스 (self-host 가능)
Neon
- **Serverless PostgreSQL** — 연결이 없으면 storage만 남기고 compute 중지
- **Branching** — git처럼 DB를 브랜치 (PR마다 독립 DB)
- Storage/Compute 분리 아키텍처 (S3 위의 PostgreSQL)
- 무료 플랜: 0.5GB + 10 branches
- 2024 DatabaseOfTheYear (DB-Engines)
Xata
- PostgreSQL + Typesafe client + Branching + Full-text search
- 2024년 pgvector 네이티브 지원
- Serverless, Schema 마이그레이션 UI
PlanetScale (2024년 PostgreSQL 런칭)
- 원래 MySQL (Vitess) 중심이었으나, 사용자 요구로 PostgreSQL 제공
- branching + deploy request (git-like workflow)
Crunchy Bridge, Aiven, ElephantSQL, Heroku Postgres
- 클래식 managed PostgreSQL 서비스들
확장 생태계 — "전용 DB가 필요 없다"
TimescaleDB — 시계열
PostgreSQL 확장. **Hypertable**(자동 파티셔닝), **Continuous Aggregates**(구체화된 뷰 자동 갱신), **Compression**(10배) 제공.
CREATE EXTENSION timescaledb;
CREATE TABLE metrics (
time TIMESTAMPTZ NOT NULL,
device_id INT,
temperature DOUBLE PRECISION,
humidity DOUBLE PRECISION
);
SELECT create_hypertable('metrics', 'time');
-- 자동 1시간 단위 파티션
SELECT add_dimension('metrics', 'device_id', number_partitions => 4);
-- 연속 집계
CREATE MATERIALIZED VIEW metrics_hourly
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', time) AS bucket,
device_id,
AVG(temperature), MAX(humidity)
FROM metrics
GROUP BY bucket, device_id;
InfluxDB 대비: SQL 사용 + 관계형 join + JSONB 지원. 쓰기 속도는 근접.
PostGIS — 지리 정보
세계에서 가장 강력한 오픈소스 GIS. 구글 맵, Airbnb, Uber가 쓰는 공간 인덱스.
CREATE EXTENSION postgis;
CREATE TABLE restaurants (
id BIGSERIAL PRIMARY KEY,
name TEXT,
location GEOGRAPHY(POINT)
);
CREATE INDEX ON restaurants USING GIST (location);
-- 반경 1km 내 레스토랑
SELECT name
FROM restaurants
WHERE ST_DWithin(
location,
ST_MakePoint(127.0276, 37.4979)::geography, -- 강남역
1000
);
Citus — 수평 확장
Microsoft가 인수한 확장. PostgreSQL을 **수평 샤딩된 분산 클러스터**로 변환.
CREATE EXTENSION citus;
-- 코디네이터 + 워커 노드
SELECT citus_add_node('worker1.db.svc', 5432);
SELECT citus_add_node('worker2.db.svc', 5432);
-- 테이블 분산
SELECT create_distributed_table('events', 'user_id');
pg_cron — 스케줄러
CREATE EXTENSION pg_cron;
SELECT cron.schedule('daily-cleanup', '0 3 * * *',
'DELETE FROM logs WHERE created_at < NOW() - INTERVAL ''30 days'''
);
pg_stat_statements — 느린 쿼리 분석
CREATE EXTENSION pg_stat_statements;
SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
pgbouncer, pgbackrest, patroni — 운영 3대장
- **pgbouncer** — 연결 풀러 (transaction/session/statement 모드)
- **pgbackrest** — 엔터프라이즈급 백업/복구
- **patroni** — HA 자동 페일오버 (etcd, ZooKeeper 기반)
인덱스 전략 — B-tree 이상의 선택지
PostgreSQL은 **7가지 인덱스 타입**을 지원:
| 타입 | 용도 | 예 |
|---|---|---|
| **B-tree** | 범용, 정렬/범위 | WHERE id = 1, ORDER BY |
| **Hash** | Equality만 | WHERE email = 'x' (9.5+ WAL 지원) |
| **GIN** | 다중 값 (array, jsonb, tsvector) | JSONB containment, FTS |
| **GIST** | 공간, 범위, 전문 | PostGIS, range types |
| **SP-GiST** | 파티션된 검색 (radix tree) | IP address, 지리 |
| **BRIN** | 거대 테이블 range | 시계열 (100GB+) |
| **Bloom** | Multi-column OR | 여러 컬럼 동시 필터 |
**BRIN의 힘** — 1TB 테이블에서 B-tree는 50GB 인덱스, BRIN은 **5MB** (1/10000). 시계열/로그에 완벽.
CREATE INDEX idx_events_time_brin ON events USING BRIN (created_at)
WITH (pages_per_range = 128);
Partial Index — 조건부 인덱싱
-- 활성 사용자만 인덱싱
CREATE INDEX idx_active_users ON users (email)
WHERE status = 'active';
-- 인덱스 크기 1/10로 감소
Covering Index (Index-Only Scan)
CREATE INDEX idx_users_email_include ON users (email) INCLUDE (name, created_at);
-- 쿼리가 email/name/created_at만 쓰면 테이블 접근 없음
EXPLAIN SELECT name, created_at FROM users WHERE email = 'a@b.com';
-- Index Only Scan
쿼리 튜닝 — EXPLAIN ANALYZE 읽기
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.name, COUNT(o.id)
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id;
출력 해석:
HashAggregate (cost=1234..1256 rows=100 actual time=5.1..5.3 rows=92 loops=1)
-> Hash Right Join (cost=10.5..1200 rows=5000 actual time=0.1..4.8 rows=4500 loops=1)
Hash Cond: (o.user_id = u.id)
-> Seq Scan on orders o (cost=0..500 rows=10000 actual time=0..3 rows=10000)
-> Hash (cost=9..9 rows=100 actual time=0.05..0.05 rows=92)
-> Index Scan using idx_users_created ON users u
(cost=0..9 rows=100 actual time=0.02..0.04 rows=92)
Index Cond: (created_at > '2024-01-01')
**주목 포인트**:
- **actual time** — 실제 시간 (ms)
- **rows** — 실제 반환 row
- **loops** — 반복 횟수 (nested loop에서 중요)
- **Seq Scan vs Index Scan** — 인덱스 미사용이면 문제
- **cost** — 상대 비용 (절대적 의미 없음)
**pgMustard, pganalyze, explain.depesz.com** — EXPLAIN 시각화 도구.
복제와 고가용성 — Patroni 패턴
┌────────────┐
│ Client │
└──────┬─────┘
│
┌──────▼─────┐
│ HAProxy │
└──┬────┬────┘
│ │
┌──▼─┐ ┌▼───┐
│ Pr │ │ St │ streaming replication
│ im │→│ an │
│ ary│ │ dby│
└──┬─┘ └────┘
│
┌──▼─────────┐
│ Patroni │←── etcd/Consul (leader election)
└────────────┘
**자동 페일오버**: Primary 다운 → Patroni가 etcd에 lease 갱신 실패 → Standby 중 하나 promote → HAProxy가 backend 교체.
보안과 RLS (Row Level Security)
-- 테이블 활성화
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- 정책: 자신의 글만 조회
CREATE POLICY my_posts_policy ON posts
FOR SELECT
USING (author_id = current_setting('app.user_id')::int);
-- 애플리케이션에서
SET app.user_id = '42';
SELECT * FROM posts; -- 자동으로 author_id=42 필터
**Supabase가 이를 대중화** — 모든 테이블에 RLS 걸고 JWT 기반 세션에서 정책 적용.
실전 배포 체크리스트 (2025)
1. **버전 선택** — PostgreSQL 16 또는 17 (LTS는 없지만 5년 지원)
2. **연결 풀러** — PgBouncer(transaction 모드) 필수
3. **백업 전략** — pgBackRest, 주간 full + 매일 incremental + WAL archive
4. **모니터링** — pg_stat_statements, pg_stat_activity, pgBadger 로그 분석
5. **autovacuum 튜닝** — 대형 테이블에 개별 설정
6. **Parameter 튜닝** — `shared_buffers` = 25% RAM, `effective_cache_size` = 75%
7. **Logical Replication** — 메이저 업그레이드 준비
8. **HA** — Patroni + 3-node etcd
9. **보안** — RLS, SSL 강제, `pg_hba.conf` 제한
10. **Index 감사** — 사용 안 하는 인덱스 제거 (`pg_stat_user_indexes`)
11. **Partitioning** — 1억 row 넘으면 파티셔닝 고려
12. **Extension 목록 통제** — trusted extension만 허용
10가지 흔한 안티패턴
1. **N+1 쿼리** — ORM이 관계별로 쿼리 재발급. `JOIN FETCH` 또는 `dataloader`.
2. **SELECT *** — 안 쓰는 컬럼까지 읽어 캐시 낭비.
3. **VARCHAR(255)** — TEXT로 충분. VARCHAR 제약은 성능 차이 없음.
4. **timestamp without time zone** — `timestamptz` 써라. UTC 저장 + 표시시 변환.
5. **모든 컬럼에 인덱스** — 쓰기 성능 파괴. 꼭 필요한 것만.
6. **VACUUM FULL을 프로덕션에서** — 테이블 rewrite + AccessExclusive lock.
7. **pg_dump을 백업으로 믿기** — logical dump는 PITR 불가. pg_basebackup + WAL 필수.
8. **DISTINCT 남발** — 데이터 모델 문제를 쿼리로 해결 시도.
9. **OFFSET으로 페이지네이션** — 대규모 OFFSET은 seq scan. **keyset pagination** (`WHERE id > last_id`) 써라.
10. **Long-running transaction** — MVCC가 dead tuple 정리 못 함. VACUUM 효과 0.
다음 글 예고 — "함수형 프로그래밍의 실용 가치" — Monad, Functor, Pure Function, Haskell, Elixir, Erlang, FRP까지
PostgreSQL이 관계형을 재해석했듯, **함수형 프로그래밍**은 JavaScript, Python, Java에까지 스며들어 현대 프로그래밍의 상식을 바꾸고 있다. React Hooks, Redux, RxJS, Swift, Rust, Kotlin 모두 함수형에서 직접 영향을 받았다. 2024-2025년에는 **Effect-TS**, **fp-ts**, **Elixir + Phoenix LiveView**, **Gleam**(BEAM 위의 타입 기반 함수형 언어)가 주류 실무에 들어왔다.
다음 글에서는:
- **왜 함수형인가 — 동시성과 분산의 시대**
- **순수 함수, 참조 투명성, 불변성의 실무 가치**
- **Monad 입문 — Option, Result, IO의 정체**
- **Functor, Applicative, Monad — 범주론 없이 이해하기**
- **Haskell의 교훈 — 지연평가, 타입 클래스, 수학자의 언어**
- **Erlang/Elixir/OTP — 통신 시대의 내구성**
- **Phoenix LiveView와 WebSocket의 재탄생**
- **RxJS, Redux, React Hooks — JavaScript의 함수형 상속**
- **Effect-TS, fp-ts — TypeScript의 함수형 실전**
- **F#, OCaml, Scala — 하이브리드 언어들의 포지션**
- **Gleam — BEAM 위의 타입 기반 함수형 언어 (2024 급부상)**
을 다룬다. '**부작용 관리**'라는 함수형의 본질이 왜 클라우드 시대에 더 중요해졌는지, 순수 함수의 수학적 미학이 어떻게 현실 시스템의 신뢰성으로 바뀌는지를 추적한다.
현재 단락 (1/456)
1996년 UC Berkeley의 Ingres(1974) 계보를 잇는 **Postgres**가 SQL을 추가해 **PostgreSQL**로 발표된 지 거의 30년. 오랜 시간 My...