icon

안동민 개발노트

14장 : NoSQL과 분산 데이터베이스

NoSQL 유형별 특성


NoSQL은 하나의 기술이 아니라, 관계형이 아닌 다양한 데이터 모델의 총칭입니다. 각 유형은 특정 문제를 해결하기 위해 설계되었으며, 데이터 모델과 접근 패턴에 따라 적합한 유형이 다릅니다.


Key-Value Store

가장 단순한 형태입니다. 키로 값을 저장하고 조회합니다. 해시 테이블과 같은 원리입니다.

Key-Value 구조
┌──────────────┬────────────────────┐
│     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)
DynamoDBAWS 관리형, 자동 스케일링, Partition Key + Sort Key
Memcached단순 캐시, 분산, 멀티스레드
etcd분산 설정 저장소, Kubernetes가 사용

Redis 자료구조

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
Redis 활용 패턴
캐시
  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)를 저장합니다. 중첩 구조를 자연스럽게 표현할 수 있습니다.

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가장 인기, 유연한 스키마, 강력한 쿼리/집계, 인덱스 지원
CouchDBHTTP API, 오프라인 동기화, MapReduce
FirestoreGoogle Cloud, 실시간 동기화, 모바일 최적

MongoDB 쿼리

MongoDB CRUD
삽입
  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}
  ])
Document DB의 스키마 설계 원칙
1. 함께 조회되는 데이터는 하나의 문서에 내장 (Embedding)
   → 조인 불필요, 한 번의 읽기로 모든 데이터 획득

2. 독립적으로 접근되는 데이터는 참조 (Referencing)
   → user 문서에 order_ids 배열 대신
   → order 문서에 user_id 참조

3. 문서 크기 제한 주의 (MongoDB 16MB)
   → 무한히 커지는 배열은 별도 컬렉션으로 분리

4. 읽기 패턴에 맞춰 설계 (Query-Driven Design)
   → RDB처럼 정규화하지 않음
   → "어떤 쿼리가 자주 실행되는가"가 설계 기준

적합한 사용 사례: 콘텐츠 관리(CMS), 사용자 프로필, 상품 카탈로그, 로그, 이벤트 저장


Wide-Column Store

행(Row)마다 다른 컬럼을 가질 수 있는 구조입니다. 컬럼 패밀리 단위로 데이터를 저장합니다.

Wide-Column 구조
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)
→ 수십억 행, 수천 개 컬럼도 효율적
→ 컬럼 패밀리 단위로 물리적 저장이 분리됨
대표 제품특징
CassandraAP 시스템, 쓰기 최적화, 링 구조, 튜닝 가능 일관성
HBaseHadoop 기반, CP 시스템, 강한 일관성, HDFS 저장
Google BigtableGCP 관리형, 분산, HBase 호환 API

Cassandra의 특징

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 없는 조건 불가!)

→ 쿼리 패턴을 먼저 결정하고 테이블을 설계해야 함
→ 같은 데이터도 쿼리 패턴별로 다른 테이블에 중복 저장
Cassandra 쓰기 최적화
쓰기 과정
  1. Commit Log에 기록 (순차 쓰기, 빠름)
  2. Memtable (메모리)에 저장
  3. 주기적으로 SSTable (디스크)로 Flush
  → 디스크 랜덤 I/O 없이 순차 쓰기만!
  → 초당 수만~수십만 건 쓰기 가능

Compaction
  → 쌓인 SSTable을 병합하여 정리
  → 삭제된 데이터는 Tombstone으로 표시 후 나중에 제거

적합한 사용 사례: 시계열 데이터, IoT 센서, 로그 분석, 추천 시스템, 메시징


Graph Database

노드(Node)와 관계(Edge)로 데이터를 표현합니다. 관계가 1급 객체(first-class citizen)입니다.

Graph 구조
(김철수)──친구──→(이영희)
   │               │
  좋아요          작성
   │               │
   ▼               ▼
(게시물A)       (게시물B)──태그──→(#여행)

노드: 엔티티 (사용자, 게시물, 태그)
엣지: 관계 (친구, 좋아요, 작성, 태그)
프로퍼티: 노드/엣지의 속성 (이름, 작성일 등)
대표 제품특징
Neo4j가장 인기, Cypher 쿼리 언어, ACID 지원
Amazon NeptuneAWS 관리형, Gremlin/SPARQL 지원
ArangoDBMulti-Model (Graph + Document + Key-Value)

Cypher 쿼리 (Neo4j)

Cypher 쿼리 예시
노드 생성
  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 path
Graph DB가 RDB보다 유리한 경우
RDB에서 "친구의 친구의 친구" 조회:
  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키:값키 검색만매우 높음설정 가능
DocumentJSON 문서높음 (풍부한 쿼리)높음설정 가능
Wide-Column행:컬럼 패밀리제한적 (PK 기반)매우 높음설정 가능
Graph노드:엣지관계 탐색 최적보통강함
RDB테이블:행:열매우 높음 (SQL)보통강함 (ACID)
NoSQL 선택 가이드
데이터가 단순한 키-값?
  → 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
  → 추천 = Neo4j

CAP 정리와 NoSQL

분산 시스템에서 Consistency(일관성), Availability(가용성), Partition Tolerance(분할 내성) 세 가지를 동시에 만족시킬 수 없다는 이론입니다.

CAP 정리
         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-ValueRedis ClusterCP마스터 장애 시 일시적 불가용
Key-ValueDynamoDBAP튜닝 가능 일관성 (최종 일관성 기본)
DocumentMongoDBCPPrimary 장애 시 선거로 새 Primary 선출
Wide-ColumnCassandraAP쿼럼 설정으로 일관성 수준 조절
Wide-ColumnHBaseCPZooKeeper 기반 강한 일관성
GraphNeo4jCA/CP클러스터 모드에서 CP

ACID vs BASE

RDB의 ACID와 대비되는 NoSQL의 BASE 모델입니다.

ACID vs BASE
ACID (RDB)
  Atomicity    → 트랜잭션 전체 성공 or 전체 실패
  Consistency  → 항상 유효한 상태 보장
  Isolation    → 트랜잭션 간 간섭 없음
  Durability   → 커밋된 데이터 영구 보존

BASE (NoSQL)
  Basically Available → 기본적으로 가용
  Soft state          → 일시적으로 불일치 허용
  Eventually consistent → 최종적으로 일치

→ ACID는 "언제나 정확" (은행 계좌)
→ BASE는 "대부분 정확, 곧 일치" (소셜 피드 좋아요 수)
Eventual Consistency 패턴
강한 일관성 (Strong Consistency)
  쓰기 완료 → 즉시 모든 노드에서 최신값 읽기
  예: 은행 잔액, 재고 수량

최종 일관성 (Eventual Consistency)
  쓰기 완료 → 일정 시간 후 모든 노드에서 최신값
  예: SNS 좋아요 수, 뉴스 피드

인과 일관성 (Causal Consistency)
  원인-결과 순서만 보장
  예: 게시물 작성 → 댓글 (댓글이 게시물보다 먼저 보이면 안 됨)

Cassandra의 일관성 수준 설정
  CONSISTENCY ONE   → 1개 노드 응답이면 OK (빠름, 약한 일관성)
  CONSISTENCY QUORUM → 과반수 노드 응답 (균형)
  CONSISTENCY ALL   → 모든 노드 응답 (느림, 강한 일관성)
  → 읽기/쓰기 각각 독립 설정 가능

NewSQL

NoSQL의 확장성과 RDB의 ACID를 결합한 새로운 유형입니다.

NewSQL 특징
목표: 분산 + ACID + SQL 호환

┌──────────────────────────────────────────┐
│              NewSQL                      │
│                                          │
│  RDB의 장점:        NoSQL의 장점:        │
│  * SQL 호환         * 수평 확장          │
│  * ACID 트랜잭션    * 자동 샤딩          │
│  * 스키마 지원      * 고가용성           │
└──────────────────────────────────────────┘
대표 제품특징
Google Spanner글로벌 분산, TrueTime API, 외부 일관성
CockroachDBPostgreSQL 호환, 자동 샤딩, 서바이벌
TiDBMySQL 호환, Raft 합의, TiKV 저장 엔진
YugabyteDBPostgreSQL 호환, 분산 트랜잭션
NewSQL vs 기존 방식 비교
전통 RDB     → 단일 노드, 수직 확장, 한계 명확
RDB + 샤딩   → 수동 샤딩, 교차 샤드 조인 어려움
NoSQL        → 수평 확장 쉬움, ACID 포기
NewSQL       → 수평 확장 + ACID + SQL (자동 분산)

적합한 경우:
  * 글로벌 분산이 필요한 금융 시스템
  * RDB 성능 한계에 도달한 대규모 서비스
  * NoSQL로는 트랜잭션 보장이 어려운 경우

NoSQL 도입 시 고려사항

도입 판단 기준
NoSQL이 적합한 경우
  * 스키마가 자주 변경됨 (프로토타이핑, 빠른 개발)
  * 대규모 데이터 (수억 건 이상)
  * 수평 확장 필요 (서버 추가로 처리량 증가)
  * 단순 읽기/쓰기 패턴 (복잡한 조인 불필요)
  * 고가용성 요구 (24/7 무중단)

RDB가 더 나은 경우
  * 복잡한 쿼리와 조인이 빈번
  * 데이터 정합성이 최우선 (금융, 결제)
  * 스키마가 안정적이고 변경이 적음
  * ACID 트랜잭션 필수
  * 데이터 규모가 단일 서버로 충분

흔한 실수
  * "유행이니까" NoSQL 도입 → 불필요한 복잡성
  * RDB 대체로 NoSQL 사용 → 조인/트랜잭션 부재로 고생
  * 단일 NoSQL로 모든 문제 해결 시도
    → Polyglot Persistence (용도별 다른 DB)가 정답

정리

유형대표 제품핵심 특징적합한 데이터
Key-ValueRedis, DynamoDB초고속, 단순세션, 캐시, 카운터
DocumentMongoDB, Firestore유연 스키마, 풍부한 쿼리CMS, 프로필, 카탈로그
Wide-ColumnCassandra, HBase대규모 쓰기, 희소 데이터시계열, IoT, 로그
GraphNeo4j, Neptune관계 탐색 최적화소셜, 추천, 사기 탐지
NewSQLSpanner, 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)는 원칙을 증명합니다. 요구사항에 맞는 데이터 모델과 일관성 수준을 선택하는 것이 핵심입니다.

다음 절에서는 분산 데이터베이스의 기초인 레플리케이션과 샤딩을 다루겠습니다.

목차