- Authors

- Name
- Youngju Kim
- @fjvbn20031
들어가며 — 왜 DB를 깊게 알아야 하는가
"SQL만 쓸 줄 알면 되지 않나?"의 반례:
- P99 레이턴시 스파이크 → 인덱스·플래너 이해 없이는 디버깅 불가
- 트랜잭션 격리 이슈 → 격리 수준 모르면 재현·수정 불가
- 저장 비용 폭증 → 파티셔닝·압축 전략 필요
- 분산 DB 선택 → 내부 구조 모르면 잘못된 선택
- Vector 검색 → 임베딩 저장소 설계 필요
2025년 시니어 엔지니어가 DB를 깊게 아는 것은 필수 교양이다.
1부 — 저장 엔진의 두 철학
1.1 B-Tree (Balanced Tree)
읽기 최적화. 전통 RDBMS(PostgreSQL, MySQL InnoDB)의 기본.
[50]
/ \
[20] [80]
/ \ / \
[10][30][70][90]
특징:
- 균형 이진 트리 (실제로는 fanout 수백)
- O(log N) 탐색
- In-place update → 쓰기 amplification 낮음
- 인덱스 구조로 널리 사용
단점: 랜덤 쓰기가 많으면 분열 + I/O.
1.2 LSM-Tree (Log-Structured Merge)
쓰기 최적화. RocksDB, Cassandra, ScyllaDB, LevelDB, ClickHouse.
Memtable (RAM)
↓ flush
L0 SSTables (디스크)
↓ compact
L1 SSTables
↓ compact
L2 SSTables ...
특징:
- 모든 쓰기는 append-only → 순차 I/O
- Compaction이 후속으로 정리
- 쓰기 속도 매우 빠름
- 공간·CPU를 compaction에 사용
단점: 읽기가 여러 레이어 탐색 → Bloom Filter 등으로 완화.
1.3 선택 기준
| 워크로드 | 선호 |
|---|---|
| OLTP (읽기 ~ 쓰기) | B-Tree (Postgres, MySQL) |
| Write-heavy (로그·메시지) | LSM (Cassandra, RocksDB) |
| 시계열 | LSM 또는 컬럼 (InfluxDB, TimescaleDB) |
| 분석 | 컬럼 (ClickHouse, DuckDB) |
1.4 2024~2025 트렌드: B-Tree와 LSM의 융합
- Aurora, Neon: 저장층 분리 (S3 같은 오브젝트 스토리지 + 로컬 캐시)
- TigerBeetle: 금융 특화, LSM + 결정성
- FoundationDB: 레이어드, ACID + Key-Value
- Postgres Incremental Materialized View: 읽기 최적 + 실시간
2부 — 인덱스 심화
2.1 인덱스 종류 8가지
| 종류 | 용도 |
|---|---|
| B-Tree | 범위 쿼리 (most common) |
| Hash | 동등 조건만 |
| GIN | 전문 검색, JSONB, Array |
| GiST | 지리·도형, 범위 타입 |
| SP-GiST | 공간 데이터 |
| BRIN | 거대 테이블의 근사 인덱스 |
| Bloom | 다중 컬럼 OR 검색 |
| Covering (INCLUDE) | 인덱스에 추가 컬럼 (Index-only scan) |
2.2 인덱스 설계 5원칙
- 쿼리가 먼저: 실제 쿼리 보고 설계
- 선택도: Selectivity 높은 컬럼 우선
- 복합 인덱스 순서: 가장 자주 쓰는 필터부터
- INCLUDE로 Covering: 테이블 접근 회피
- 사용하지 않는 인덱스는 삭제: 쓰기 성능 저해
2.3 인덱스 안 타는 이유 Top 10
- 함수 적용:
WHERE LOWER(email) = ...→ Expression Index 필요 - 암시적 형 변환:
WHERE id = '123'(id는 int) - 선행 와일드카드:
LIKE '%foo'→ Full scan - OR 조건: 옵티마이저가 포기할 수 있음 → UNION
- Not Equals (
!=): 대부분 인덱스 안 씀 - NULL 비교: Postgres는
IS NULL인덱스 가능, 설계 주의 - 데이터가 작음: 플래너가 Sequential Scan 선호
- 복합 인덱스 순서 틀림:
(a, b)인덱스에WHERE b = ? - Statistics 오래됨: ANALYZE 필요
- 인덱스 Bloat: REINDEX 필요
3부 — 쿼리 플래너와 EXPLAIN
3.1 플래너의 역할
SQL (선언) → 실행 계획 (절차)로 변환. 비용 기반 최적화 (CBO).
3.2 PostgreSQL EXPLAIN 읽기
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT u.name, COUNT(o.id)
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.country = 'KR'
GROUP BY u.name;
출력 예:
HashAggregate (cost=1234..5678 rows=100)
Group Key: u.name
-> Hash Join (cost=500..1200 rows=10000)
Hash Cond: o.user_id = u.id
-> Seq Scan on orders o (cost=0..800 rows=100000)
-> Hash (cost=300..300 rows=500)
-> Index Scan on users_country_idx (cost=0..300 rows=500)
읽는 법:
- 안쪽부터 바깥으로
cost=startup..totalrows: 예상 row 수 (ANALYZE와 실제 비교)actual time: 실제 시간Buffers: 메모리·디스크 접근
3.3 중요 노드 유형
| 노드 | 의미 |
|---|---|
| Seq Scan | 전체 스캔 (작은 테이블·선택도 낮을 때 OK) |
| Index Scan | 인덱스 사용 |
| Index Only Scan | 인덱스만으로 해결 (빠름) |
| Bitmap Heap Scan | 인덱스로 찾은 후 테이블 일괄 |
| Nested Loop Join | 작은 outer + 인덱스 있는 inner |
| Hash Join | 해시 테이블 만들어 매칭 |
| Merge Join | 양쪽 정렬된 경우 |
| Hash Aggregate | GROUP BY |
| Sort | ORDER BY |
3.4 Plan Hint는 Postgres에 없다
MySQL·Oracle은 힌트 있음. Postgres는 ANALYZE + 통계 목표 + random_page_cost 조정으로 유도.
4부 — 트랜잭션 격리
4.1 ACID 재정의
- Atomicity: 전부 or 무
- Consistency: 제약 위반 없음
- Isolation: 동시 실행이 순차 실행처럼
- Durability: 커밋되면 유지
4.2 격리 수준 4단계 (SQL-92)
| 수준 | 방지 |
|---|---|
| Read Uncommitted | (거의 아무것도 안 방지) |
| Read Committed | Dirty Read |
| Repeatable Read | + Non-repeatable Read |
| Serializable | + Phantom |
4.3 DB별 기본 격리 수준
| DB | 기본 |
|---|---|
| PostgreSQL | Read Committed |
| MySQL InnoDB | Repeatable Read |
| Oracle | Read Committed (Serializable 옵션) |
| SQL Server | Read Committed |
4.4 Snapshot Isolation vs Serializable
- Snapshot Isolation (SI): 각 트랜잭션이 시작 시점 스냅샷을 봄
- 단점: Write Skew 가능
- Serializable (MVCC SSI): SI + 충돌 감지 → abort
- Postgres는
SERIALIZABLE로 SSI 제공
- Postgres는
4.5 Write Skew 예
두 의사 중 적어도 한 명 당직 필요:
-- T1: on_call을 읽고 2명 → OK
-- T2: 동시에 on_call 읽고 2명 → OK
-- 둘 다 자기 상태를 off-call로 변경
-- 커밋 → 0명 당직. 제약 위반
Serializable에서만 감지됨.
4.6 Deadlock
둘 이상의 트랜잭션이 서로의 락을 기다림. DB가 감지 → 1개 희생 (abort).
방지:
- 락 순서 일관 (ID 오름차순)
SELECT FOR UPDATE NOWAIT- 재시도 로직 (멱등성 + 백오프)
5부 — MVCC (Multi-Version Concurrency Control)
5.1 MVCC 기본
"쓰기는 읽기를 막지 않고, 읽기는 쓰기를 막지 않는다."
각 row의 여러 버전 보관 + 트랜잭션이 자기 스냅샷 버전 읽음.
5.2 Postgres의 MVCC
- Tuple 헤더: xmin (생성 트랜잭션), xmax (삭제 트랜잭션)
- Vacuum: 죽은 튜플 정리. 필수.
- autovacuum: 자동, 하지만 튜닝 필요
5.3 Vacuum Bloat 문제
- Long-running transaction이 autovacuum 막음
- Table/Index Bloat → 성능 저하
- 해결: pg_repack, VACUUM FULL, 주기적 모니터링
5.4 MySQL InnoDB MVCC
- Undo Log에 이전 버전
- Purge Thread가 정리
- Clustered Index (Primary Key로 물리 정렬)
6부 — 파티셔닝과 샤딩
6.1 수직 vs 수평
- 수직: 컬럼 분할 (거의 안 씀)
- 수평 (Horizontal): row 분할. Partitioning·Sharding.
6.2 Partitioning (단일 DB 내)
Postgres 네이티브 (10+):
CREATE TABLE events (
id bigserial,
event_time timestamptz,
data jsonb
) PARTITION BY RANGE (event_time);
CREATE TABLE events_2025_01 PARTITION OF events
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
장점: 파티션 가지치기, 관리 용이 (DROP old partition). 한계: 단일 DB 내. 단일 서버 용량 제약.
6.3 Sharding (여러 DB)
데이터를 여러 서버에 분산.
샤딩 키 선택:
- 카디널리티 높음
- 접근 패턴 고려
- 리밸런싱 용이
- Hot Shard 방지
샤딩 방법:
- Hash: 균등, 범위 쿼리 ❌
- Range: 범위 쿼리 OK, Hot Shard 위험
- Directory-based: 유연, 메타데이터 관리
6.4 Cross-shard 쿼리
기본적으로 비쌈. JOIN·ORDER BY가 여러 샤드 걸치면 어플리케이션 레벨 merge.
해결:
- 같은 샤드에 관련 데이터 (tenant_id로)
- Read Replica + Materialized View
- OLAP은 별도 (데이터 웨어하우스)
6.5 분산 SQL DB (샤딩 자동)
- CockroachDB
- YugabyteDB
- Spanner
- TiDB
- Vitess (MySQL 위)
- Citus (Postgres 확장)
7부 — Replication과 HA
7.1 Replication 방식
- Physical (WAL/Binlog): 바이트 단위, 정확
- Logical: SQL/Row 단위, 유연 (이기종·부분)
7.2 Postgres Replication 2025
- Streaming Replication: WAL 전송
- Logical Replication (10+): 테이블 단위
- Synchronous 옵션: 확실하지만 느림
- Patroni: HA 관리 (표준)
7.3 Failover 패턴
Primary 장애 감지
↓ (consensus: etcd·Patroni)
Standby 중 최신 선택
↓
새 Primary 승격 + 응용이 DSN 전환
↓
기존 Primary는 Rebuild
핵심: Split-brain 방지 (Fencing).
7.4 Read Replica 활용
- Read-heavy 워크로드 분산
- Replication lag 감수
- "Read-your-writes"는 Primary에서
8부 — PostgreSQL의 2025년 독주
8.1 왜 Postgres가 이겼나
2024~2025년 DB 선택에서 Postgres가 사실상 기본값이 된 이유:
- 확장성: 수많은 Extension
- JSONB: NoSQL 기능 내장
- pgvector: Vector DB 기능
- PostGIS: GIS 최강
- Timescale: 시계열
- Citus: 분산
- Logical Replication: CDC·마이그레이션
- 라이선스: PostgreSQL License (관대)
- 커뮤니티: 안정적, 대기업 종속 없음
- 클라우드 관리형: Aurora, RDS, Cloud SQL, Neon, Supabase
8.2 Postgres Extension Top 15 (2025)
| Extension | 용도 |
|---|---|
| pgvector | Vector Search |
| PostGIS | 지리 |
| TimescaleDB | 시계열 |
| Citus | 분산 |
| pg_partman | 파티션 관리 |
| pg_cron | 스케줄 |
| pg_stat_statements | 쿼리 성능 |
| hypopg | 가상 인덱스 |
| pg_repack | 온라인 재구성 |
| pg_hint_plan | 플랜 힌트 |
| pg_trgm | 유사 검색 |
| unaccent | 악센트 제거 |
| uuid-ossp | UUID 생성 |
| pgcrypto | 암호 |
| hstore | 키-값 |
8.3 2024~2025 Postgres 생태계
- Neon: Serverless Postgres, 브랜치
- Supabase: Postgres + Auth + Realtime
- Xata: DevEx 우선
- Aurora Postgres: AWS 관리형
- ParadeDB: 검색 확장
9부 — NoSQL: 언제 쓸까
9.1 NoSQL 5 카테고리
| 카테고리 | 예시 | 용도 |
|---|---|---|
| Document | MongoDB, Firestore | 스키마 유연 |
| Key-Value | Redis, DynamoDB | 빠른 조회 |
| Wide-column | Cassandra, ScyllaDB, HBase | 대규모 쓰기 |
| Graph | Neo4j, Neptune | 관계 중심 |
| Time-Series | InfluxDB, Timescale | 시계열 |
9.2 2025 현실: "Postgres로 충분"
대부분의 NoSQL 사용 사례를 Postgres가 커버:
- Document → JSONB
- Key-Value → UNLOGGED 테이블 또는 Redis 병행
- Time-Series → TimescaleDB
- Graph → Apache AGE extension
여전히 NoSQL 선택:
- 글로벌 저지연 (DynamoDB Global Table)
- 초대형 쓰기 (Cassandra)
- 그래프 알고리즘 특화 (Neo4j)
9.3 Redis의 위치
인메모리 캐시·큐·랭킹·Rate Limit. "하나쯤은 다들 갖고 있는" 도구.
2024 라이선스 변경 → Valkey 포크 (AWS·Google 주도). 선택지 늘어남.
10부 — Vector DB
10.1 Vector DB가 필요한 이유
LLM 임베딩 검색·추천 등을 위한 근사 최근접 이웃 (ANN) 검색.
10.2 ANN 알고리즘
- HNSW (Hierarchical Navigable Small World): 빠름·정확, 메모리 ↑
- IVF (Inverted File): 클러스터링 기반
- PQ (Product Quantization): 메모리 절감
- HNSW + PQ: 하이브리드
10.3 2025 Vector DB 비교
| DB | 특징 |
|---|---|
| pgvector | Postgres 확장, 통합 최고 |
| Qdrant | Rust, 빠름, 필터링 강함 |
| Weaviate | 하이브리드 검색, GraphQL API |
| Milvus | 대규모, Zilliz |
| Pinecone | SaaS, 관리 편함 |
| LanceDB | 파일 기반, 임베디드 |
| Turbopuffer | Serverless |
10.4 2025 추천
- 간단한 RAG: pgvector (기존 Postgres에 추가)
- 대규모·고급 필터: Qdrant 또는 Weaviate
- 완전 관리형: Pinecone
- 로컬/엣지: LanceDB
10.5 pgvector 예시
CREATE EXTENSION vector;
CREATE TABLE documents (
id bigserial PRIMARY KEY,
content text,
embedding vector(1536)
);
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
-- 검색
SELECT content
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 10;
11부 — DB 로드맵 6개월
Month 1: SQL 심화
- Window Function, CTE, Recursive
- 트랜잭션 격리·Deadlock 실습
- 인덱스 설계
Month 2: Postgres 내부
- EXPLAIN 완전 이해
- MVCC·Vacuum
- WAL·Replication
Month 3: Storage Engine
- DDIA 3장 (Storage)
- B-Tree / LSM 구현 체험
- RocksDB·Cassandra 학습
Month 4: 분산 DB
- CockroachDB·Spanner 구조
- Sharding 설계
- Replication 운영
Month 5: Vector DB
- pgvector 실전
- HNSW 이해
- RAG 데이터 레이어 구축
Month 6: 최적화·운영
- Slow Query 분석
- 인덱스 튜닝
- Connection Pooling (PgBouncer)
- Schema Migration 무중단 (Postgres 심화)
12부 — DB 체크리스트 12
- B-Tree vs LSM-Tree 선택 기준을 안다
- Postgres 인덱스 타입 5가지를 용도별로 안다
- 인덱스 안 타는 이유 5가지를 말할 수 있다
- EXPLAIN ANALYZE 출력을 읽을 수 있다
- 트랜잭션 격리 4단계를 방지 현상과 함께 안다
- Write Skew를 설명할 수 있다
- MVCC와 Vacuum의 관계를 안다
- Partitioning vs Sharding 차이를 안다
- Hash vs Range 샤딩 트레이드오프를 안다
- Postgres Extension Top 5를 안다
- pgvector와 Qdrant 선택 기준을 안다
- Connection Pool (PgBouncer) 필요성을 안다
13부 — DB 안티패턴 10
- ORM만 믿고 쿼리 점검 없음: N+1, 과다 컬럼 로드 흔함
- 인덱스 하나씩 무제한 추가: 쓰기 성능 저하. 사용 안 되는 거 삭제
- Long transaction: MVCC bloat. 트랜잭션 짧게
- 기본 격리 수준 맹신: Read Committed 한계 모르고 Write Skew 버그
- Connection 하나씩 관리: Pool 없이 수천 connection → Postgres 터짐
- JSONB에 모든 걸: 스키마 강점 포기. 정형은 컬럼으로
- 샤딩 키 잘못: Hot Shard → 재샤딩 지옥
- Migration 무계획 downtime: 무중단 마이그레이션 전략 필수
- Vacuum 튜닝 무시: 저도 모르게 테이블 bloat 10배
- DB를 메시지 큐로: 가능하지만 비추. Redis/Kafka 사용
마치며 — DB는 "프로덕션 시스템의 중력"이다
애플리케이션은 바꿔도, DB 스키마는 쉽게 못 바꾼다. DB는 프로덕션 시스템에서 가장 관성이 큰 부분.
그래서 처음에 잘 설계해야 한다:
- 인덱스 설계로 5년 버틸 구조
- 파티션·샤딩으로 10배 성장 대비
- 격리 수준으로 데이터 무결성 보장
- 관측성·백업으로 재앙 대비
2025년의 DB 엔지니어링은:
- Postgres First: 대부분은 이것으로 해결
- Vector DB: LLM 시대 필수
- 분산 DB: 초대형 서비스에만
- NoSQL: 명확한 이유 있을 때만
다음 글 예고 — "네트워크 완전 가이드: HTTP/3·QUIC·TLS 1.3·gRPC·WebSocket·CDN"
Season 2 Ep 14는 인터넷의 배관, 네트워크. 다음 글은:
- TCP vs UDP vs QUIC
- HTTP/1.1 → 2 → 3 진화
- TLS 1.3 Handshake
- gRPC vs REST vs GraphQL vs tRPC
- WebSocket vs SSE vs Long Polling
- CDN 아키텍처 (Cloudflare, Fastly)
보이지 않는 전선, 다음 글에서.