Skip to content
Published on

PostgreSQL이 다시 1위가 된 이유 심화 가이드 — pgvector, HNSW, JSONB, MVCC, Supabase, Neon, TimescaleDB, AI 시대의 DB 전략 (2025)

Authors

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')

트랜잭션 200UPDATE 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 IsolationBEGIN 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)
                         └─> COMMITWAL 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 JSONBMongoDB
SchemaOptional (유연)Schemaless
TransactionACID (기본)ACID (4.0+)
JoinSQL JOIN 완벽 지원$lookup (제한적)
IndexGIN, B-tree, BRINB-tree, compound
AggregationSQL + windowaggregation 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 TimeIndex SizeQPSRecall
Seq Scan--~10100%
IVFFlat10분6GB~50095%
HNSW30분8GB~300098%

pgvector vs 전용 벡터 DB

DB타입1M 벡터 QPS장점단점
pgvectorPostgreSQL 확장3,000SQL + metadata join수천만 이상에서 부하
PineconeSaaS 전용15,000+관리 필요 없음비싸고 lock-in
Weaviate오픈소스10,000GraphQL, 하이브리드별도 운영 부담
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 Backuppg_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
HashEquality만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+)
BloomMulti-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 zonetimestamptz 써라. 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 급부상)

을 다룬다. '부작용 관리'라는 함수형의 본질이 왜 클라우드 시대에 더 중요해졌는지, 순수 함수의 수학적 미학이 어떻게 현실 시스템의 신뢰성으로 바뀌는지를 추적한다.