NoSQL 유형별 특성
NoSQL은 하나의 기술이 아니라, 관계형이 아닌 다양한 데이터 모델의 총칭입니다. 각 유형은 특정 문제를 해결하기 위해 설계되었으며, 데이터 모델과 접근 패턴에 따라 적합한 유형이 다릅니다.
Key-Value Store
가장 단순한 형태입니다. 키로 값을 저장하고 조회합니다. 해시 테이블과 같은 원리입니다.
┌──────────────┬────────────────────┐
│ Key │ Value │
├──────────────┼────────────────────┤
│ user:1001 │ {"name": "김철수"} │
│ session:abc │ {token, exp, ...} │
│ cart:1001 │ [item1, item2] │
│ counter:page │ 42857 │
└──────────────┴────────────────────┘
연산
GET user:1001 → {"name": "김철수"}
SET user:1002 {...} → 저장
DEL session:abc → 삭제
INCR counter:page → 42858 (원자적 증가)| 대표 제품 | 특징 |
|---|---|
| Redis | 인메모리, 초고속, 다양한 자료구조 (String, List, Set, Hash, Sorted Set) |
| DynamoDB | AWS 관리형, 자동 스케일링, Partition Key + Sort Key |
| Memcached | 단순 캐시, 분산, 멀티스레드 |
| etcd | 분산 설정 저장소, Kubernetes가 사용 |
Redis 자료구조
String: 가장 기본, 단순 키-값
SET name "김철수"
GET name → "김철수"
List: 순서 있는 목록 (큐/스택)
LPUSH queue "task1"
RPOP queue → "task1"
Set: 고유 값 집합
SADD tags:post1 "여행" "맛집"
SMEMBERS tags:post1 → {"여행", "맛집"}
SINTER tags:post1 tags:post2 → 교집합
Hash: 필드-값 쌍 (객체 표현)
HSET user:1001 name "김철수" age 28
HGET user:1001 name → "김철수"
HGETALL user:1001 → name="김철수", age=28
Sorted Set: 점수 기반 정렬 집합 (순위표)
ZADD leaderboard 100 "player1"
ZADD leaderboard 200 "player2"
ZREVRANGE leaderboard 0 9 → TOP 10캐시
DB 쿼리 결과를 Redis에 저장 → 동일 요청 시 DB 접근 생략
SET cache:user:1001 "{...}" EX 3600 (1시간 TTL)
세션
SET session:token123 "{user_id:1001}" EX 1800 (30분)
분산 락
SET lock:resource NX EX 10 (없을 때만 설정, 10초 TTL)
실시간 순위
Sorted Set으로 점수 기반 실시간 랭킹
Rate Limiting
INCR api:user:1001:count
EXPIRE api:user:1001:count 60 (1분당 요청 수 제한)적합한 사용 사례: 세션 저장, 캐싱, 실시간 순위표, 장바구니, 분산 락, 메시지 큐
Document Store
JSON/BSON 형태의 문서(Document)를 저장합니다. 중첩 구조를 자연스럽게 표현할 수 있습니다.
{
"_id": "order_001",
"user": {
"id": 1001,
"name": "김철수"
},
"items": [
{"product": "노트북", "price": 1500000, "qty": 1},
{"product": "마우스", "price": 50000, "qty": 2}
],
"total": 1600000,
"status": "shipped",
"created_at": "2024-01-15T10:30:00Z"
}
→ RDB였다면 orders + order_items + users 3개 테이블 조인 필요
→ Document DB는 하나의 문서에 모두 포함| 대표 제품 | 특징 |
|---|---|
| MongoDB | 가장 인기, 유연한 스키마, 강력한 쿼리/집계, 인덱스 지원 |
| CouchDB | HTTP API, 오프라인 동기화, MapReduce |
| Firestore | Google Cloud, 실시간 동기화, 모바일 최적 |
MongoDB 쿼리
삽입
db.orders.insertOne({
user: {id: 1001, name: "김철수"},
items: [{product: "노트북", price: 1500000}],
total: 1500000
})
조회
db.orders.find({
"user.id": 1001,
total: {$gte: 100000}
})
수정
db.orders.updateOne(
{_id: "order_001"},
{$set: {status: "delivered"}}
)
집계 파이프라인
db.orders.aggregate([
{$match: {status: "shipped"}},
{$group: {_id: "$user.id", total: {$sum: "$total"}}},
{$sort: {total: -1}},
{$limit: 10}
])1. 함께 조회되는 데이터는 하나의 문서에 내장 (Embedding)
→ 조인 불필요, 한 번의 읽기로 모든 데이터 획득
2. 독립적으로 접근되는 데이터는 참조 (Referencing)
→ user 문서에 order_ids 배열 대신
→ order 문서에 user_id 참조
3. 문서 크기 제한 주의 (MongoDB 16MB)
→ 무한히 커지는 배열은 별도 컬렉션으로 분리
4. 읽기 패턴에 맞춰 설계 (Query-Driven Design)
→ RDB처럼 정규화하지 않음
→ "어떤 쿼리가 자주 실행되는가"가 설계 기준적합한 사용 사례: 콘텐츠 관리(CMS), 사용자 프로필, 상품 카탈로그, 로그, 이벤트 저장
Wide-Column Store
행(Row)마다 다른 컬럼을 가질 수 있는 구조입니다. 컬럼 패밀리 단위로 데이터를 저장합니다.
Row Key │ Column Family: 기본정보 │ Column Family: 활동
────────────┼──────────────────────────────┼─────────────────
user:1001 │ name="김철수" age=28 │ login="2024-01-15"
user:1002 │ name="이영희" dept="개발" │ login="2024-01-14" posts=42
user:1003 │ name="박민수" │ (없음)
→ 각 행의 컬럼 구성이 다를 수 있음 (Sparse)
→ 수십억 행, 수천 개 컬럼도 효율적
→ 컬럼 패밀리 단위로 물리적 저장이 분리됨| 대표 제품 | 특징 |
|---|---|
| Cassandra | AP 시스템, 쓰기 최적화, 링 구조, 튜닝 가능 일관성 |
| HBase | Hadoop 기반, CP 시스템, 강한 일관성, HDFS 저장 |
| Google Bigtable | GCP 관리형, 분산, HBase 호환 API |
Cassandra의 특징
테이블 정의
CREATE TABLE sensor_data (
sensor_id TEXT,
timestamp TIMESTAMP,
temperature DOUBLE,
humidity DOUBLE,
PRIMARY KEY (sensor_id, timestamp)
) WITH CLUSTERING ORDER BY (timestamp DESC);
Partition Key: sensor_id → 어느 노드에 저장할지 결정
Clustering Key: timestamp → 파티션 내 정렬 순서
쿼리 제약
✓ WHERE sensor_id = 'A' AND timestamp > '...'
✗ WHERE temperature > 30 (Partition Key 없는 조건 불가!)
→ 쿼리 패턴을 먼저 결정하고 테이블을 설계해야 함
→ 같은 데이터도 쿼리 패턴별로 다른 테이블에 중복 저장쓰기 과정
1. Commit Log에 기록 (순차 쓰기, 빠름)
2. Memtable (메모리)에 저장
3. 주기적으로 SSTable (디스크)로 Flush
→ 디스크 랜덤 I/O 없이 순차 쓰기만!
→ 초당 수만~수십만 건 쓰기 가능
Compaction
→ 쌓인 SSTable을 병합하여 정리
→ 삭제된 데이터는 Tombstone으로 표시 후 나중에 제거적합한 사용 사례: 시계열 데이터, IoT 센서, 로그 분석, 추천 시스템, 메시징
Graph Database
노드(Node)와 관계(Edge)로 데이터를 표현합니다. 관계가 1급 객체(first-class citizen)입니다.
(김철수)──친구──→(이영희)
│ │
좋아요 작성
│ │
▼ ▼
(게시물A) (게시물B)──태그──→(#여행)
노드: 엔티티 (사용자, 게시물, 태그)
엣지: 관계 (친구, 좋아요, 작성, 태그)
프로퍼티: 노드/엣지의 속성 (이름, 작성일 등)| 대표 제품 | 특징 |
|---|---|
| Neo4j | 가장 인기, Cypher 쿼리 언어, ACID 지원 |
| Amazon Neptune | AWS 관리형, Gremlin/SPARQL 지원 |
| ArangoDB | Multi-Model (Graph + Document + Key-Value) |
Cypher 쿼리 (Neo4j)
노드 생성
CREATE (u:User {name: "김철수", age: 28})
관계 생성
MATCH (a:User {name: "김철수"}), (b:User {name: "이영희"})
CREATE (a)-[:FRIEND {since: "2020-01-01"}]->(b)
친구의 친구 찾기 (2단계 탐색)
MATCH (me:User {name: "김철수"})-[:FRIEND*2]->(fof:User)
WHERE fof <> me
RETURN DISTINCT fof.name
→ RDB로 같은 쿼리: 자기 조인 2번 + 중첩 서브쿼리
→ Graph DB: 직관적인 패턴 매칭으로 표현
최단 경로
MATCH path = shortestPath(
(a:User {name: "김철수"})-[:FRIEND*..6]-(b:User {name: "박민수"})
)
RETURN pathRDB에서 "친구의 친구의 친구" 조회:
SELECT DISTINCT f3.name
FROM friends f1
JOIN friends f2 ON f1.friend_id = f2.user_id
JOIN friends f3 ON f2.friend_id = f3.user_id
WHERE f1.user_id = 1001;
→ N단계 관계 = N번 조인 = 성능 급격히 저하
Graph DB에서 같은 조회:
MATCH (:User {id:1001})-[:FRIEND*3]->(target)
RETURN target
→ 관계 탐색에 최적화된 구조
→ 깊이가 늘어나도 성능 저하 적음적합한 사용 사례: 소셜 네트워크, 추천 엔진, 사기 탐지, 지식 그래프, 네트워크 분석
유형별 비교
| 유형 | 데이터 모델 | 쿼리 유연성 | 확장성 | 일관성 |
|---|---|---|---|---|
| Key-Value | 키:값 | 키 검색만 | 매우 높음 | 설정 가능 |
| Document | JSON 문서 | 높음 (풍부한 쿼리) | 높음 | 설정 가능 |
| Wide-Column | 행:컬럼 패밀리 | 제한적 (PK 기반) | 매우 높음 | 설정 가능 |
| Graph | 노드:엣지 | 관계 탐색 최적 | 보통 | 강함 |
| RDB | 테이블:행:열 | 매우 높음 (SQL) | 보통 | 강함 (ACID) |
데이터가 단순한 키-값?
→ Key-Value (Redis, DynamoDB)
문서 형태로 중첩 구조?
→ Document (MongoDB, Firestore)
대규모 시계열/로그 데이터?
→ Wide-Column (Cassandra, HBase)
데이터 간 관계가 핵심?
→ Graph (Neo4j, Neptune)
복잡한 쿼리 + 트랜잭션 + 정합성?
→ RDB (Oracle, MySQL, PostgreSQL)
실무에서는 Polyglot Persistence:
→ 메인 데이터 = RDB
→ 캐시 = Redis
→ 검색 = Elasticsearch
→ 로그 = Cassandra or MongoDB
→ 추천 = Neo4jCAP 정리와 NoSQL
분산 시스템에서 Consistency(일관성), Availability(가용성), Partition Tolerance(분할 내성) 세 가지를 동시에 만족시킬 수 없다는 이론입니다.
Consistency
/\
/ \
/ \
/ CA \
/ (RDBMS)\
/──────────\
/ \
/ CP AP \
/ (HBase) (Cass)\
/_________________ \
Availability Partition
Tolerance
CA: 분할 내성 포기 → 전통적 RDBMS (단일 노드)
CP: 가용성 포기 → HBase, MongoDB, Redis Cluster
AP: 일관성 포기 → Cassandra, DynamoDB, CouchDB
→ 분산 시스템에서는 P(Partition Tolerance)는 필수
→ 실질적으로 CP vs AP 중 선택| 유형 | 대표 제품 | CAP 분류 | 설명 |
|---|---|---|---|
| Key-Value | Redis Cluster | CP | 마스터 장애 시 일시적 불가용 |
| Key-Value | DynamoDB | AP | 튜닝 가능 일관성 (최종 일관성 기본) |
| Document | MongoDB | CP | Primary 장애 시 선거로 새 Primary 선출 |
| Wide-Column | Cassandra | AP | 쿼럼 설정으로 일관성 수준 조절 |
| Wide-Column | HBase | CP | ZooKeeper 기반 강한 일관성 |
| Graph | Neo4j | CA/CP | 클러스터 모드에서 CP |
ACID vs BASE
RDB의 ACID와 대비되는 NoSQL의 BASE 모델입니다.
ACID (RDB)
Atomicity → 트랜잭션 전체 성공 or 전체 실패
Consistency → 항상 유효한 상태 보장
Isolation → 트랜잭션 간 간섭 없음
Durability → 커밋된 데이터 영구 보존
BASE (NoSQL)
Basically Available → 기본적으로 가용
Soft state → 일시적으로 불일치 허용
Eventually consistent → 최종적으로 일치
→ ACID는 "언제나 정확" (은행 계좌)
→ BASE는 "대부분 정확, 곧 일치" (소셜 피드 좋아요 수)강한 일관성 (Strong Consistency)
쓰기 완료 → 즉시 모든 노드에서 최신값 읽기
예: 은행 잔액, 재고 수량
최종 일관성 (Eventual Consistency)
쓰기 완료 → 일정 시간 후 모든 노드에서 최신값
예: SNS 좋아요 수, 뉴스 피드
인과 일관성 (Causal Consistency)
원인-결과 순서만 보장
예: 게시물 작성 → 댓글 (댓글이 게시물보다 먼저 보이면 안 됨)
Cassandra의 일관성 수준 설정
CONSISTENCY ONE → 1개 노드 응답이면 OK (빠름, 약한 일관성)
CONSISTENCY QUORUM → 과반수 노드 응답 (균형)
CONSISTENCY ALL → 모든 노드 응답 (느림, 강한 일관성)
→ 읽기/쓰기 각각 독립 설정 가능NewSQL
NoSQL의 확장성과 RDB의 ACID를 결합한 새로운 유형입니다.
목표: 분산 + ACID + SQL 호환
┌──────────────────────────────────────────┐
│ NewSQL │
│ │
│ RDB의 장점: NoSQL의 장점: │
│ * SQL 호환 * 수평 확장 │
│ * ACID 트랜잭션 * 자동 샤딩 │
│ * 스키마 지원 * 고가용성 │
└──────────────────────────────────────────┘| 대표 제품 | 특징 |
|---|---|
| Google Spanner | 글로벌 분산, TrueTime API, 외부 일관성 |
| CockroachDB | PostgreSQL 호환, 자동 샤딩, 서바이벌 |
| TiDB | MySQL 호환, Raft 합의, TiKV 저장 엔진 |
| YugabyteDB | PostgreSQL 호환, 분산 트랜잭션 |
전통 RDB → 단일 노드, 수직 확장, 한계 명확
RDB + 샤딩 → 수동 샤딩, 교차 샤드 조인 어려움
NoSQL → 수평 확장 쉬움, ACID 포기
NewSQL → 수평 확장 + ACID + SQL (자동 분산)
적합한 경우:
* 글로벌 분산이 필요한 금융 시스템
* RDB 성능 한계에 도달한 대규모 서비스
* NoSQL로는 트랜잭션 보장이 어려운 경우NoSQL 도입 시 고려사항
NoSQL이 적합한 경우
* 스키마가 자주 변경됨 (프로토타이핑, 빠른 개발)
* 대규모 데이터 (수억 건 이상)
* 수평 확장 필요 (서버 추가로 처리량 증가)
* 단순 읽기/쓰기 패턴 (복잡한 조인 불필요)
* 고가용성 요구 (24/7 무중단)
RDB가 더 나은 경우
* 복잡한 쿼리와 조인이 빈번
* 데이터 정합성이 최우선 (금융, 결제)
* 스키마가 안정적이고 변경이 적음
* ACID 트랜잭션 필수
* 데이터 규모가 단일 서버로 충분
흔한 실수
* "유행이니까" NoSQL 도입 → 불필요한 복잡성
* RDB 대체로 NoSQL 사용 → 조인/트랜잭션 부재로 고생
* 단일 NoSQL로 모든 문제 해결 시도
→ Polyglot Persistence (용도별 다른 DB)가 정답정리
| 유형 | 대표 제품 | 핵심 특징 | 적합한 데이터 |
|---|---|---|---|
| Key-Value | Redis, DynamoDB | 초고속, 단순 | 세션, 캐시, 카운터 |
| Document | MongoDB, Firestore | 유연 스키마, 풍부한 쿼리 | CMS, 프로필, 카탈로그 |
| Wide-Column | Cassandra, HBase | 대규모 쓰기, 희소 데이터 | 시계열, IoT, 로그 |
| Graph | Neo4j, Neptune | 관계 탐색 최적화 | 소셜, 추천, 사기 탐지 |
| NewSQL | Spanner, CockroachDB | 분산 + ACID + SQL | 글로벌 금융, 대규모 트랜잭션 |
NoSQL 유형 선택의 핵심은 데이터 접근 패턴입니다. 어떤 쿼리를 가장 자주 실행하는가에 맞는 데이터 모델을 선택해야 합니다. RDB가 만능이 아니듯, NoSQL도 만능이 아닙니다. 각 유형의 강점과 한계를 이해하고 적재적소에 활용하는 것이 중요합니다.
CAP 정리는 분산 DB 선택의 이론적 근거를 제공합니다. CP 시스템은 일관성, AP 시스템은 가용성에 우선순위를 둡니다. 실무에서는 대부분의 NoSQL이 일관성 수준을 튜닝할 수 있으므로 절대적 분류보다는 기본 성향으로 이해하는 것이 적절합니다.
시험에서는 각 NoSQL 유형의 대표 제품, 데이터 모델 특성, CAP 분류, ACID vs BASE 차이가 자주 출제됩니다. 특히 Key-Value Store와 Document Store의 적합한 사용 사례를 구분하는 문제, Wide-Column Store가 시계열 데이터에 적합한 이유(쓰기 최적화, 파티션 기반 분산), Graph DB가 다단계 관계 탐색에서 RDB보다 우수한 이유(인접 리스트 저장 구조, 인덱스 프리 인접성)를 명확히 이해해야 합니다.
또한 Polyglot Persistence(다중 저장소 전략)가 현대 시스템의 표준이라는 점도 기억해야 합니다. 하나의 DB로 모든 요구를 충족하기보다, 메인 데이터(RDB), 캐시(Redis), 검색(Elasticsearch), 분석(Cassandra/HBase) 등 용도별로 최적의 저장소를 조합하는 것이 실무의 일반적인 접근입니다.
NewSQL은 NoSQL의 확장성과 RDB의 ACID를 모두 지원하는 차세대 기술로, Google Spanner, CockroachDB, TiDB 등이 대표 제품입니다. NoSQL 유형 분류와 함께 NewSQL의 위치를 파악해두면 전체 데이터베이스 기술 스펙트럼을 이해하는 데 도움이 됩니다.
이러한 다양한 데이터베이스 기술의 존재는 하나의 최적 해가 없다(No One Size Fits All)는 원칙을 증명합니다. 요구사항에 맞는 데이터 모델과 일관성 수준을 선택하는 것이 핵심입니다.
다음 절에서는 분산 데이터베이스의 기초인 레플리케이션과 샤딩을 다루겠습니다.