Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

> **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...

작성 글자: 0원문 글자: 16,703작성 단락: 0/456