[DB] Cassandra: LSM-tree, quorum, eventually consistent
Cassandra, Apache Cassandra, LSM tree, memtable, SSTable, quorum read/write, CQL, ScyllaDB
정의
Apache Cassandra = 마스터리스 (peer-to-peer) 분산 KV 스토어. write 우선, eventually consistent, 수평 무한 확장. 2008 Facebook 의 Dynamo + BigTable 결합.
ScyllaDB = Cassandra 호환 C++ 재구현. 10x throughput.
핵심 특성
| Cassandra | |
|---|---|
| 토폴로지 | peer-to-peer (master 없음) |
| 일관성 | 튜닝 가능 (per query) |
| 스토리지 | LSM-tree (write-optimized) |
| 쿼리 언어 | CQL (SQL-like) |
| 인덱스 | primary key + secondary (제한) |
| Join | 없음 |
| 트랜잭션 | 제한적 (LWT, lightweight transaction) |
LSM-Tree (Log-Structured Merge-Tree)
flowchart TB
Write[Write] --> CommitLog["Commit Log<br/>(WAL)"]
Write --> Memtable["Memtable<br/>(메모리)"]
Memtable -.flush.-> SST1[SSTable L0]
SST1 -.compact.-> SST2[SSTable L1]
SST2 -.compact.-> SST3[SSTable L2]
SST3 -.compact.-> SST4[SSTable L3]
| 컴포넌트 | 의미 |
|---|---|
| Commit Log | WAL (crash recovery) |
| Memtable | 메모리 정렬 자료 |
| SSTable | 불변 정렬 파일 (디스크) |
| Compaction | SSTable 들을 큰 SSTable 로 병합 |
외부 정렬 머지의 일반 직관. LSM-tree 의 compaction 도 level 들을 순차 머지.
LSM vs B-tree
| 항목 | B-tree | LSM |
|---|---|---|
| Write | in-place (random) | append (sequential) |
| Read | 1 lookup | 여러 SSTable 검색 |
| Write amplification | 작음 | 큼 (compaction) |
| Read amplification | 작음 | 큼 |
| 적합 | OLTP read-heavy | write-heavy, log-like |
클러스터 토폴로지
flowchart LR
subgraph Ring[Token Ring]
N1[Node A<br/>token 0]
N2[Node B<br/>token 1/3]
N3[Node C<br/>token 2/3]
end
N1 --- N2 --- N3 --- N1
Key1[hash key=42] -->|시계방향| N2
- consistent hashing ring.
- N 개 노드, Replication Factor (RF) 만큼 복제.
- 전체가 peer. master 없음.
Consistency Level (per query)
| Level | 의미 |
|---|---|
ANY | 어떤 노드라도 응답 (write) |
ONE | 1 노드 응답 |
TWO, THREE | 2 / 3 노드 |
QUORUM | 과반 (RF/2 + 1) |
LOCAL_QUORUM | 같은 DC 안 과반 |
EACH_QUORUM | 모든 DC 의 과반 |
ALL | 모든 replica |
Quorum 의 W + R > N 조건
flowchart LR
Write[W = QUORUM] --> N[RF = 3]
Read[R = QUORUM]
Cond["W + R > N → 항상 최신 read"]
Write --> Cond
Read --> Cond
RF = 3, W = QUORUM (2), R = QUORUM (2). W + R = 4 > 3 → 최소 1 노드는 양쪽 모두 봄 → strong consistency.
Read Path
sequenceDiagram
Client->>Coord: SELECT
Coord->>R1: read
Coord->>R2: read
par 응답 대기
R1-->>Coord: v1 (ts=100)
R2-->>Coord: v2 (ts=110)
end
Coord->>Coord: 최신 (ts=110) 반환
Coord->>R1: read repair (백그라운드)
Last-write-wins by timestamp. 시계 어긋남 이 데이터 정정 의 함정.
CQL (Cassandra Query Language)
CREATE KEYSPACE shop
WITH replication = { 'class': 'NetworkTopologyStrategy', 'dc1': 3 };
CREATE TABLE orders (
user_id UUID,
order_id TIMEUUID,
total INT,
status TEXT,
PRIMARY KEY (user_id, order_id)
) WITH CLUSTERING ORDER BY (order_id DESC);
-- partition key = user_id, clustering key = order_id
SELECT * FROM orders WHERE user_id = ? AND order_id > ?;
INSERT INTO orders (user_id, order_id, total, status)
VALUES (?, now(), ?, ?);
| 키 | 의미 |
|---|---|
| Partition Key | partition (노드) 결정 |
| Clustering Key | partition 안의 정렬 |
LWT (Lightweight Transaction)
INSERT INTO users (id, email) VALUES (?, ?) IF NOT EXISTS;
UPDATE users SET email = ? WHERE id = ? IF email = ?;
- Paxos 기반. 4 round-trip.
- 느림. 가끔만 사용.
적합 / 부적합
flowchart TD
Q{사용 패턴}
Q --> Good[적합]
Q --> Bad[부적합]
Good --> G1[Write-heavy log]
Good --> G2[Time-series]
Good --> G3[IoT telemetry]
Good --> G4[수십 TB 데이터]
Bad --> B1[복잡한 ad-hoc 쿼리]
Bad --> B2[강한 트랜잭션]
Bad --> B3[Join 많음]
Bad --> B4[작은 데이터셋]
흔한 함정
WARNING
- Secondary index 남용 = 거의 항상 전체 노드 fan-out. 모든 access pattern 에 전용 테이블.
- Tombstone (삭제 마커) 누적 = 압축 안 되면 read 폭증.
gc_grace_seconds+ 정기 repair. - Cross-partition query = 비싸다. partition key 디자인이 결정적.
- Strong consistency 기대 = QUORUM 으로도 시계 어긋남 시 잘못된 결과. NTP + 보수적 timestamp.
Compaction 전략
| 전략 | 특징 | 적합 |
|---|---|---|
| STCS (Size-Tiered) | 비슷한 크기 SSTable 병합 | write-heavy |
| LCS (Leveled) | 고정 크기 level 유지 | read-heavy, 공간 효율 |
| TWCS (Time-Window) | 시간 창 단위 병합 | time-series, TTL |
ALTER TABLE orders
WITH compaction = {
'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'HOURS',
'compaction_window_size': 1
};
TIP
Time-series 데이터는 TWCS + TTL 조합. 오래된 데이터가 자동 삭제되어 tombstone 누적 없음.
데이터 모델링 원칙
flowchart TD
Q["쿼리 패턴 먼저 정의"] --> T["테이블 설계"]
T --> P["Partition Key: 균등 분산 여부"]
P -->|불균등| FP["다른 partition key 선택"]
FP --> P
P -->|균등| C["Clustering Key: 정렬 방향 결정"]
C --> OK["테이블 확정"]
- 쿼리 주도 설계: 먼저 쿼리를 정의하고, 그에 맞는 테이블을 만든다.
- 1 쿼리 = 1 테이블: 각 access pattern 에 전용 테이블.
- Partition 크기: 100MB 이하 권장. 너무 크면 hot partition.
- Denormalization: 중복 저장은 의도적. 조인 없음.
- TTL 활용: 만료 데이터는
INSERT ... USING TTL로 자동 삭제.
nodetool 운영 명령어
# 클러스터 상태 확인
nodetool status
# 특정 키가 어느 노드에 있는지 확인
nodetool getendpoints shop orders <partition_key>
# 수동 compaction 실행
nodetool compact shop orders
# replica 동기화 (repair)
nodetool repair shop
# Tombstone 현황 확인
nodetool cfstats shop.orders | grep "Tombstone"
# 노드 드레인 (점검 전)
nodetool drain
Bloom Filter
각 SSTable 은 Bloom Filter 를 가진다. 읽기 시 SSTable 을 열기 전에 키가 없음을 빠르게 판별.
- False positive 가능: 있다고 했지만 없음 → 실제 SSTable 열어 확인
- False negative 없음: 없다고 하면 진짜 없음
bloom_filter_fp_chance로 정밀도 조정 (낮을수록 메모리 더 사용)
ALTER TABLE orders
WITH bloom_filter_fp_chance = 0.01; -- 기본 0.1, 낮을수록 정확
Write Path 상세
sequenceDiagram
Client->>Coord: WRITE (CL = QUORUM)
Coord->>N1: write
Coord->>N2: write
Coord->>N3: write (hinted handoff)
par 응답 대기
N1-->>Coord: ack
N2-->>Coord: ack
end
Coord-->>Client: OK
Note over N3: 오프라인 시 힌트 보관, 복귀 후 전달
- Hinted Handoff: 대상 노드 다운 시 코디네이터가 임시 보관. 복귀 후 데이터 전달.
- commit log 에 먼저 기록 후 memtable 업데이트: 내구성 보장.
ANYconsistency 는 hint 만으로 성공 → 실제 replica 에 아직 없을 수 있음.
Read Repair & Anti-Entropy
| 메커니즘 | 시점 | 설명 |
|---|---|---|
| Read Repair | 읽기 직후 백그라운드 | 응답 노드 간 불일치 발견 시 자동 수정 |
| Anti-Entropy | 수동 (nodetool repair) | Merkle tree 비교로 전체 데이터 동기화 |
WARNING
Repair 를 주기적으로 실행하지 않으면 노드 간 데이터 드리프트가 누적됩니다. gc_grace_seconds 이내에 반드시 repair 완료.
관련 위키
이 글의 용어 (5개)
- [DB] DynamoDB: PK + SK, single-table design, GSI / LSIdatabase-internals
- 정의 DynamoDB 는 AWS 의 fully managed key-value + document NoSQL. low-latency, infinite scale, schemale…
- [DB] MongoDB: 문서 DB, WiredTiger, replica set, aggregationdatabase-internals
- 정의 MongoDB 는 BSON (binary JSON) 문서 기반 NoSQL DB. schema-less 라기보다 flexible schema. 2026 시점 Atlas (ma…
- [DB] Sharding vs Partitioning: 수평 확장의 두 얼굴database-internals
- 정의 | | Partitioning | Sharding | |---|---|---| | 범위 | 한 DB 안 | 여러 DB 노드 | | 목적 | 큰 테이블 관리 | 수평 확장 (…
- [DB] WAL: Write-Ahead Log, crash recovery, replication 의 토대database-internals
- 정의 Write-Ahead Log (WAL) = 데이터 변경 전 로그를 먼저 디스크에 기록. 모든 모던 RDB / KV / 분산 시스템의 내구성 기반. 핵심 약속: 로그가 디스크…
- [Distributed Systems] Consensus: Raft, Paxosdistributed-systems
- 정의 Consensus (합의): 여러 노드가 같은 값에 동의 하는 분산 알고리즘. CAP 의 C 보장의 기반. 용도: - Leader election: 1개 leader 선출 …
💬 댓글