학습 목표
NoSQL 네 가지 데이터 모델(KV·문서·와이드컬럼·그래프)과 대표 제품을 구분할 수 있다.
스키마 유연성과 정규화·JOIN 트레이드오프를 access pattern 기준으로 설명할 수 있다.
CAP에서 분할(partition) 시 일관성 vs 가용성 선택을 설명할 수 있다.
워크로드(lookup·range·관계 탐색·ad-hoc 집계)별 적합한 모델을 고를 수 있다.
RDB와 NoSQL이 공존하는 이유 — polyglot persistence — 를 설명할 수 있다.
문제 상황
- 상품 속성이 매주 바뀌는데
ALTER TABLE배포가 월 1회 — 스키마 병목 - 피드 쓰기 TPS 8만 — 단일 PostgreSQL WAL·connection 한계 (샤딩 비용 큼)
- MongoDB에 주문·결제 원장 — 다중 문서 TX·정산 취약
- Redis에 주문 영구 저장 — AOF만으로 감사·복잡 쿼리 불가
- Cassandra range scan 없이 전 partition fan-out — 쿼리 설계 실패
- "NoSQL이 빠르다" — 패턴 없이 벤더만 선택
앞서 복제·샤딩·분산 TX — RDB 확장 한계를 봤다. 다른 데이터 모델이 왜 등장했는지, 엔진 관점에서 정리한다.
1. RDB 한계와 NoSQL
NoSQL — "SQL 없음"이 아니라 관계형·ACID·JOIN 중심 모델 밖의 저장소 계열.
| RDB 강점 (앞서) | 확장·유연성 병목 |
|---|---|
| ACID·제약 | 수평 write — shard·2PC 비용 |
| JOIN·ad-hoc SQL | 스키마 migration 배포 |
| 성숙 도구 | wide row·nested 데이터 비효율 |
Scale-up exhausted → replication (read) → sharding (write)
Still mismatch? → model fit: embed doc, partition key, graph edge
- NoSQL ≠ 항상 빠름 — access pattern에 맞을 때 이득
- Architecture 데이터 저장소 선택 — 시스템 조립; 여기서는 모델·엔진 동작
2. 네 가지 데이터 모델

| 유형 | 단위 | 쿼리 | 대표 | 잘 맞는 패턴 |
|---|---|---|---|---|
| Key-value | key → blob |
get/put by key | Redis, DynamoDB | 세션·캐시·rate limit |
| Document | JSON/BSON 문서 | key + 필드 filter | MongoDB | aggregate 단위 CRUD |
| Wide-column | partition + sort → columns | partition key 필수 | Cassandra, HBase | append log·시계열 |
| Graph | node·edge | 관계 traverse | Neo4j | 추천·권한·경로 |
Key-value
# code/kv-session.example
SET session:u42 "{\"cart\":[101,205],\"ttl\":3600}" EX 3600
GET session:u42
INCR rate:api:u42
- 초저지연 · TTL — 원장·복잡 쿼리 부적합
- DynamoDB도 KV 계열 — partition key + optional sort key
Document
// code/document-order.example
{
"_id": "ord-9001",
"user_id": 42,
"status": "paid",
"line_items": [
{ "sku": "A1", "qty": 2, "price": 9900 },
{ "sku": "B3", "qty": 1, "price": 15000 }
],
"shipping": { "city": "Seoul", "zip": "06234" }
}
- 한 문서 = aggregate — JOIN 대신 embed
- 스키마 유연 — 필드 추가가 migration 없이 가능 (운영 검증은 필요)
Wide-column
-- code/wide-column-events.cql (Cassandra CQL)
CREATE TABLE events_by_user (
user_id bigint,
event_time timestamp,
event_type text,
payload text,
PRIMARY KEY (user_id, event_time)
) WITH CLUSTERING ORDER BY (event_time DESC);
INSERT INTO events_by_user (user_id, event_time, event_type, payload)
VALUES (42, '2026-07-04T10:00:00Z', 'click', '{"page":"/p/101"}');
- 행 =
(partition key, sort key) → columns - 쿼리는 테이블 설계 시 고정 — partition key 없는 scan 비용 큼
- 쓰기 분산·append에 강함
Graph
// code/graph-social.cypher
MATCH (u:User {id: 42})-[:FOLLOWS]->(friend)-[:POSTED]->(p:Post)
RETURN friend.name, p.title
LIMIT 20;
- 관계가 1급 — SQL 재귀 JOIN·many-hop 비용 큼
- 스케일·운영 비용 — 도메인이 진짜 graph일 때
3. 스키마 유연성 vs JOIN

| RDB (정규화) | Document (embed) | |
|---|---|---|
| 중복 | 최소 | 허용 (read locality) |
| 변경 | ALTER·FK | 필드 추가 쉬움 |
| 쿼리 | JOIN·집계 자유 | 문서 경계 안 |
| 일관성 | TX across tables | 단일 문서 atomic (제품별) |
-- RDB: 3-table join
SELECT o.id, u.name, li.sku, li.qty
FROM orders o
JOIN users u ON u.id = o.user_id
JOIN order_items li ON li.order_id = o.id
WHERE o.id = 9001;
Document: one read by _id → line_items embedded
Trade-off: update sku price in catalog → denormalized copies stale
| 선택 기준 | |
|---|---|
| 함께 read/write? | embed |
| 독립 lifecycle·many-to-many? | 정규화·RDB |
| ad-hoc report? | RDB·warehouse |
- 앞서 역정규화 — 같은 트레이드오프, 모델이 기본값을 바꿈
4. CAP과 일관성
CAP — 분산 저장소에서 network partition 발생 시 세 가지 중 동시 만족 불가:

| 의미 | |
|---|---|
| C (Consistency) | 모든 노드 같은 값 (linearizable 에 가까움) |
| A (Availability) | 모든 요청 응답 (에러·timeout 없음) |
| P (Partition tolerance) | 네트워크 분할 허용 (분산 현실) |
Partition happens → choose C or A (not both in strict CAP)
No partition → CA achievable on single node / sync cluster
| 경향 | 예 | 특징 |
|---|---|---|
| CP | etcd, ZooKeeper, 동기 replica | 일관성 · partition 시 일부 unavailable |
| AP | Cassandra, DynamoDB (default) | 가용 · eventual consistency |
| CA | 단일 RDB 노드 | 분할 없음 가정 |
| PACELC (Kleppmann) | partition 없을 때 Latency vs Consistency |
|---|---|
| Cassandra | EL — low latency |
| DynamoDB | tunable — strong read 옵션 |
- 앞서 replication lag — AP 계열의 stale read 동일 문제
- 앞서 saga — eventual consistency 수용 시 분산 TX 대체
5. Access pattern과 선택

| 패턴 | 1차 모델 | 주의 |
|---|---|---|
| Point lookup by id | KV · Document | hot key |
| Range by time/user | Wide-column · RDB partition | partition key 설계 |
| Ad-hoc JOIN·집계 | RDB | NoSQL secondary |
| Graph traverse 2~5 hop | Graph | SQL 재귀 한계 |
| Full-text search | Search engine (ES) | source of truth 별도 |
| Cache | KV (Redis) | TTL·invalidation |
결정 순서
- 데이터 shape — nested doc vs flat relation
- 쿼리 — 고정 access path vs ad-hoc SQL
- 일관성 — strong TX vs eventual 허용
- 규모 — single node · shard · native partition
- 운영 — backup·CDC·팀 스킬
Wrong: "We use Mongo" first
Right: access pattern → consistency → scale → product
6. RDB와 공존
| 역할 | 저장소 | |
|---|---|---|
| 원장 | PostgreSQL | orders, payments |
| 카탈로그 | Document | flexible attributes |
| 세션 | Redis | TTL |
| 이벤트 log | Wide-column / Kafka | append |
| 검색 | Elasticsearch | read model |
- Polyglot persistence — 서비스·워크로드마다 최적 엔진 — 운영 표면적 증가
- CQRS — write RDB + read cache/search — 캐시·CDC 편과 연결
- NewSQL (Spanner, CockroachDB) — SQL + 분산 TX — 비용·운영 tradeoff
7. 체크리스트
| 항목 | 확인 |
|---|---|
| Pattern | 90% 쿼리가 partition/key 포함? |
| Truth | 원장 하나 — 나머지 파생? |
| TX | cross-document·cross-shard 필요? |
| Schema | 유연 = 검증·migration 계획 있음? |
| CAP | partition 시 C vs A 명시? |
| Ops | backup·repair·compaction runbook? |
8. 정리
- NoSQL — 모델 특화 — KV·Document·Wide-column·Graph
- 스키마 유연 ↔ JOIN·정규화 — access pattern으로 선택
- CAP — partition 시 C vs A — lag·eventual 이해
- RDB 대체 아님 — 원장·집계는 관계형 강함
- Part 4 마무리 — 다음 Part 5 실무(pool·ORM·캐시)
다음에 다룰 것
- Connection Pool과 ORM
- pool 고갈, N+1, 지연 로딩
해당 내용은 Database System Concepts, 7/E (Avraham Silberschatz, Henry F. Korth, S. Sudarshan), Designing Data-Intensive Applications (Martin Kleppmann) 의 내용을 기반으로 합니다.
'데이터베이스 따라잡기' 카테고리의 다른 글
| 캐시와 DB (0) | 2026.07.06 |
|---|---|
| Connection Pool과 ORM (0) | 2026.07.05 |
| 분산 트랜잭션 개요 (0) | 2026.07.03 |
| 파티셔닝과 샤딩 (0) | 2026.07.02 |
| 복제 (0) | 2026.07.01 |