Skip to content
Published on

Apache Cassandra 분산 와이드 컬럼 데이터베이스: 데이터 모델링부터 프로덕션 운영까지

Authors
  • Name
    Twitter
Cassandra

들어가며

대규모 분산 시스템에서 초당 수십만 건의 쓰기 요청을 안정적으로 처리하면서도 높은 가용성을 유지해야 하는 경우, 전통적인 RDBMS만으로는 한계가 명확하다. Apache Cassandra는 Amazon Dynamo의 분산 아키텍처와 Google Bigtable의 데이터 모델을 결합하여 Facebook에서 탄생한 와이드 컬럼 NoSQL 데이터베이스다. 2024년 9월 공식 릴리스된 Cassandra 5.0은 Storage Attached Index(SAI), 벡터 검색, Trie 기반 SSTable, Unified Compaction Strategy(UCS), Dynamic Data Masking 등 대규모 기능 업데이트를 포함하여 AI/ML 시대에 맞는 진화를 이루었다.

이 글에서는 Cassandra의 분산 아키텍처 핵심 원리부터 실전 데이터 모델링, CQL 활용법, 읽기/쓰기 경로 분석, Compaction 전략 비교, JVM 튜닝, 그리고 장애 시나리오별 복구 절차까지 프로덕션 운영에 필요한 모든 내용을 다룬다. 마지막으로 C++로 재구현된 호환 데이터베이스인 ScyllaDB와의 상세 비교도 함께 정리한다.

1. Cassandra 아키텍처 핵심

1.1 피어 투 피어 아키텍처

Cassandra는 마스터-슬레이브 구조가 아닌 피어 투 피어(Peer-to-Peer) 아키텍처를 채택한다. 모든 노드가 동등한 역할을 수행하므로 단일 장애점(SPOF)이 존재하지 않는다. 클라이언트는 클러스터의 어떤 노드에든 연결하여 읽기/쓰기 요청을 보낼 수 있으며, 해당 노드가 코디네이터 역할을 맡아 적절한 레플리카 노드로 요청을 라우팅한다.

1.2 Gossip 프로토콜

노드 간의 상태 정보는 Gossip 프로토콜을 통해 전파된다. 각 노드는 1초마다 최대 3개의 다른 노드에게 자신과 알고 있는 노드들의 상태 정보를 전달한다. 이 방식은 중앙 집중형 상태 관리에 비해 네트워크 파티션에 대한 내성이 강하며, O(log N) 시간 내에 전체 클러스터에 정보가 전파된다. 각 노드의 Gossip 메시지에는 generation number와 heartbeat counter가 포함되어 있어 오래된 정보를 자동으로 무시할 수 있다.

1.3 Consistent Hashing과 Vnodes

Cassandra는 Consistent Hashing을 사용하여 데이터를 클러스터 전체에 균등하게 분산한다. 파티션 키를 Murmur3 해시 함수로 해싱하면 -2^63 ~ 2^63-1 범위의 토큰 값이 생성되고, 이 토큰 값에 해당하는 노드에 데이터가 배치된다.

초기 Cassandra에서는 각 노드에 하나의 토큰만 할당했으나, 이는 노드 추가/제거 시 데이터 재분배가 비균등하게 이루어지는 문제가 있었다. **Vnodes(Virtual Nodes)**는 이를 해결하기 위해 도입되었으며, 각 물리 노드에 기본 256개의 가상 토큰을 할당한다. 이를 통해 다음과 같은 이점을 얻을 수 있다.

  • 노드 추가/제거 시 데이터 재분배가 여러 노드에 걸쳐 균등하게 수행됨
  • 이기종 하드웨어 환경에서 성능이 좋은 노드에 더 많은 Vnode를 할당할 수 있음
  • 리빌드(rebuild) 및 복구(repair) 작업이 클러스터 전체에 분산되어 병렬로 수행됨
# cassandra.yaml - Vnodes 설정
num_tokens: 16 # Cassandra 5.0에서는 16이 권장값 (기존 256에서 감소)
allocate_tokens_for_local_replication_factor: 3

Cassandra 5.0부터는 num_tokens 기본값이 16으로 줄어들었다. 토큰 수가 지나치게 많으면 gossip 메시지 크기가 커지고 repair 작업의 오버헤드가 증가하기 때문이다. 대부분의 프로덕션 환경에서 16개면 충분한 분산을 달성한다.

1.4 Replication과 일관성 수준

데이터의 복제본 수는 키스페이스 생성 시 **Replication Factor(RF)**로 지정한다. 일반적으로 프로덕션 환경에서는 RF=3을 사용하며, 각 데이터 센터별로 다른 RF를 설정할 수도 있다. 읽기/쓰기 시 **Consistency Level(CL)**을 지정하여 얼마나 많은 레플리카로부터 응답을 받아야 성공으로 간주할지 결정한다.

핵심 공식: W + R > RF일 때 강한 일관성(Strong Consistency)이 보장된다. 예를 들어 RF=3, CL=QUORUM(2)이면 W(2) + R(2) = 4 > 3이므로 읽기 시 항상 최신 데이터를 반환한다.

2. 데이터 모델링 원칙

2.1 쿼리 기반 설계(Query-Driven Design)

RDBMS에서는 정규화를 통해 데이터를 설계한 뒤 필요한 쿼리를 작성하지만, Cassandra에서는 먼저 쿼리를 정의하고 그에 맞게 테이블을 설계한다. 같은 데이터가 여러 테이블에 비정규화되어 저장될 수 있으며, 이는 의도적인 설계다. JOIN이 없고 테이블 스캔이 사실상 불가능하므로, 각 테이블은 정확히 하나의 쿼리 패턴을 효율적으로 서빙하도록 만들어져야 한다.

2.2 파티션 키(Partition Key) 설계

파티션 키는 데이터가 어느 노드에 저장될지를 결정하는 핵심 요소다. 파티션 키 설계 시 반드시 고려해야 할 사항은 다음과 같다.

  • 균등 분산: 파티션 키의 카디널리티가 충분히 높아야 데이터가 클러스터 전체에 골고루 분산된다
  • 파티션 크기 제한: 단일 파티션이 100MB를 초과하지 않도록 설계한다 (권장 50MB 이하)
  • 핫스팟 방지: 특정 파티션에 읽기/쓰기가 집중되지 않도록 키를 선택한다

2.3 클러스터링 키(Clustering Key) 설계

클러스터링 키는 파티션 내에서 데이터의 정렬 순서를 결정한다. 시간순 조회, 범위 쿼리, Top-N 조회 등에 핵심적인 역할을 한다.

-- 시계열 데이터 테이블: 센서 ID로 파티션, 시간 역순 정렬
CREATE TABLE sensor_readings (
    sensor_id    UUID,
    reading_date DATE,
    reading_time TIMESTAMP,
    temperature  DOUBLE,
    humidity     DOUBLE,
    pressure     DOUBLE,
    PRIMARY KEY ((sensor_id, reading_date), reading_time)
) WITH CLUSTERING ORDER BY (reading_time DESC)
  AND compaction = {'class': 'TimeWindowCompactionStrategy',
                    'compaction_window_unit': 'DAYS',
                    'compaction_window_size': 1};

이 예시에서 (sensor_id, reading_date)가 복합 파티션 키로 사용되어 하루 단위로 파티션이 나뉘며, reading_time은 클러스터링 키로 시간 역순 정렬을 수행한다. 이렇게 하면 특정 센서의 특정 날짜 최신 데이터를 효율적으로 조회할 수 있다.

2.4 안티패턴: 피해야 할 설계

안티패턴문제점해결 방법
무한 성장 파티션파티션이 무한히 커져 성능 저하시간 단위 버킷팅으로 파티션 분리
카디널리티 낮은 파티션 키핫스팟 발생복합 파티션 키 사용으로 분산
과도한 Secondary Index 사용분산 쿼리로 성능 저하Materialized View 또는 별도 테이블
ALLOW FILTERING 남용전체 클러스터 스캔쿼리에 맞는 테이블 재설계
큰 BLOB 컬럼파티션 크기 초과외부 스토리지 참조로 변경

3. CQL 실전

3.1 키스페이스와 테이블 생성

-- 멀티 데이터센터 키스페이스 생성
CREATE KEYSPACE ecommerce
WITH replication = {
    'class': 'NetworkTopologyStrategy',
    'dc-seoul': 3,
    'dc-tokyo': 2
}
AND durable_writes = true;

USE ecommerce;

-- 주문 테이블: 사용자별 주문 내역 조회에 최적화
CREATE TABLE orders_by_user (
    user_id      UUID,
    order_date   DATE,
    order_id     TIMEUUID,
    product_name TEXT,
    quantity     INT,
    total_price  DECIMAL,
    status       TEXT,
    PRIMARY KEY ((user_id, order_date), order_id)
) WITH CLUSTERING ORDER BY (order_id DESC)
  AND default_time_to_live = 31536000  -- 1년 TTL
  AND gc_grace_seconds = 864000;       -- 10일

-- Cassandra 5.0: SAI(Storage Attached Index) 활용
CREATE INDEX ON orders_by_user(status) USING 'sai';
CREATE INDEX ON orders_by_user(product_name) USING 'sai';

3.2 데이터 삽입과 조회

-- 일관성 수준을 지정한 데이터 삽입
CONSISTENCY QUORUM;

INSERT INTO orders_by_user (user_id, order_date, order_id, product_name, quantity, total_price, status)
VALUES (
    550e8400-e29b-41d4-a716-446655440000,
    '2026-03-09',
    now(),
    '무선 키보드 MK850',
    2,
    156000,
    'CONFIRMED'
) USING TTL 31536000;

-- 특정 사용자의 오늘 주문 조회 (최신순)
SELECT order_id, product_name, total_price, status
FROM orders_by_user
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000
  AND order_date = '2026-03-09'
LIMIT 20;

-- SAI를 활용한 상태별 필터링 (Cassandra 5.0)
SELECT * FROM orders_by_user
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000
  AND order_date = '2026-03-09'
  AND status = 'CONFIRMED';

-- 경량 트랜잭션 (Compare-and-Set)
UPDATE orders_by_user
SET status = 'SHIPPED'
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000
  AND order_date = '2026-03-09'
  AND order_id = d3a3b4e0-dcb2-11e6-bf26-cec0c932ce01
IF status = 'CONFIRMED';

3.3 Python 드라이버 활용

from cassandra.cluster import Cluster
from cassandra.policies import DCAwareRoundRobinPolicy, TokenAwarePolicy
from cassandra.query import SimpleStatement, ConsistencyLevel
from cassandra import WriteTimeout, ReadTimeout, Unavailable
import uuid
from datetime import date

# 클러스터 연결 (토큰 인식 로드밸런싱)
cluster = Cluster(
    contact_points=['10.0.1.10', '10.0.1.11', '10.0.1.12'],
    load_balancing_policy=TokenAwarePolicy(
        DCAwareRoundRobinPolicy(local_dc='dc-seoul')
    ),
    protocol_version=5
)
session = cluster.connect('ecommerce')

# Prepared Statement 사용 (성능 최적화 필수)
insert_stmt = session.prepare("""
    INSERT INTO orders_by_user
    (user_id, order_date, order_id, product_name, quantity, total_price, status)
    VALUES (?, ?, now(), ?, ?, ?, ?)
""")
insert_stmt.consistency_level = ConsistencyLevel.QUORUM

# 배치 삽입 (같은 파티션 내에서만 사용)
from cassandra.query import BatchStatement, BatchType

batch = BatchStatement(batch_type=BatchType.UNLOGGED)
user_id = uuid.UUID('550e8400-e29b-41d4-a716-446655440000')
today = date.today()

orders = [
    ('노트북 스탠드', 1, 45000, 'PENDING'),
    ('USB-C 허브', 1, 67000, 'PENDING'),
    ('모니터 암', 2, 89000, 'PENDING'),
]

for product, qty, price, status in orders:
    batch.add(insert_stmt, (user_id, today, product, qty, price, status))

try:
    session.execute(batch)
    print("배치 삽입 성공")
except WriteTimeout as e:
    print(f"쓰기 타임아웃: {e.consistency}, 수신 응답: {e.received_responses}/{e.required_responses}")
except Unavailable as e:
    print(f"노드 부족: 필요 {e.required_replicas}, 가용 {e.alive_replicas}")

# 비동기 쿼리 실행
from cassandra.concurrent import execute_concurrent_with_args

select_stmt = session.prepare("""
    SELECT order_id, product_name, total_price, status
    FROM orders_by_user
    WHERE user_id = ? AND order_date = ?
    LIMIT 50
""")
select_stmt.consistency_level = ConsistencyLevel.LOCAL_QUORUM

# 여러 날짜에 대해 동시 조회
dates = [date(2026, 3, d) for d in range(1, 10)]
params = [(user_id, d) for d in dates]

results = execute_concurrent_with_args(session, select_stmt, params, concurrency=10)
for success, result in results:
    if success:
        for row in result:
            print(f"주문: {row.product_name}, 금액: {row.total_price}, 상태: {row.status}")

cluster.shutdown()

4. 읽기/쓰기 경로 분석

4.1 쓰기 경로 (Write Path)

Cassandra의 쓰기 경로는 다음 순서로 진행된다.

  1. 코디네이터 노드가 파티션 키를 해싱하여 레플리카 노드를 결정한다
  2. 각 레플리카 노드에서 CommitLog에 순차적으로 기록한다 (fsync)
  3. Memtable(메모리 내 정렬 구조)에 데이터를 삽입한다
  4. Memtable이 임계치에 도달하면 SSTable로 플러시(flush)된다
  5. 코디네이터가 지정된 Consistency Level만큼의 레플리카 응답을 받으면 클라이언트에 성공을 반환한다

쓰기가 빠른 이유는 CommitLog 기록이 순차 I/O이고, Memtable은 메모리 연산이기 때문이다. 디스크에 대한 랜덤 I/O가 발생하지 않으므로 SSD뿐 아니라 HDD에서도 높은 쓰기 성능을 보인다.

4.2 읽기 경로 (Read Path)

읽기 경로는 쓰기에 비해 복잡하다.

  1. 코디네이터가 레플리카 노드를 결정한다
  2. CL에 따라 가장 빠른 레플리카에게 전체 데이터를, 나머지에게는 다이제스트(digest)를 요청한다
  3. 각 레플리카에서 Memtable 확인 후 Bloom Filter를 통해 SSTable 후보를 빠르게 걸러낸다
  4. Partition IndexCompression Offset Map을 통해 디스크에서 데이터를 읽는다
  5. 여러 SSTable의 데이터를 **병합(merge)**하여 최신 버전을 선택한다
  6. 다이제스트 불일치 시 Read Repair가 트리거되어 비동기적으로 불일치 데이터를 수정한다

Cassandra 5.0의 Trie 기반 SSTable은 기존 인덱스 대비 룩업 효율이 크게 향상되었으며, 인덱스 서머리 및 키 캐시가 불필요해졌다. 특히 파티션 내 수백만 행이 존재하는 경우 성능 차이가 두드러진다.

5. Compaction 전략 비교

Compaction은 여러 SSTable을 병합하여 삭제된 데이터(tombstone)를 제거하고 읽기 성능을 최적화하는 핵심 백그라운드 프로세스다.

5.1 전통적 Compaction 전략

전략동작 방식최적 워크로드공간 증폭쓰기 증폭읽기 증폭
STCS (Size-Tiered)비슷한 크기의 SSTable을 병합쓰기 집중높음 (최대 2x)낮음높음
LCS (Leveled)레벨별 비겹치는 SSTable 유지읽기 집중낮음 (1.1x)높음낮음
TWCS (Time-Window)시간 윈도우별 STCS 적용시계열/TTL 데이터낮음낮음중간

5.2 Unified Compaction Strategy (UCS) - Cassandra 5.0

Cassandra 5.0에서 도입된 **UCS(Unified Compaction Strategy)**는 STCS, LCS, TWCS의 장점을 하나의 알고리즘으로 통합한 새로운 전략이다. 핵심 아이디어는 SSTable의 **밀도(density)**를 기준으로 그룹을 형성하여 컴팩션 대상을 선택하는 것이다.

UCS의 주요 특징은 다음과 같다.

  • 토큰 범위 샤딩: 토큰 범위를 고정 세그먼트로 분할하여 각 세그먼트가 독립적으로 컴팩션을 수행, 병렬성을 극대화
  • 적응형 동작: scaling_parameters 설정으로 tiered(STCS와 유사)와 leveled(LCS와 유사) 동작을 연속적으로 조절 가능
  • 시간 윈도우 지원: base_shard_counttarget_sstable_size로 TWCS와 유사한 시간 기반 컴팩션 동작 구현
  • 안정적 선택: STCS처럼 비슷한 크기의 SSTable을 찾는 것이 아니라 밀도 기반 밴딩을 사용하므로 선택이 더 안정적이고 예측 가능
# UCS 설정 예시
compaction:
  class: UnifiedCompactionStrategy
  scaling_parameters: T4 # T=Tiered, L=Leveled, 숫자=fan factor
  target_sstable_size: 1GiB
  base_shard_count: 4

6. 튜닝 파라미터

6.1 JVM 튜닝

Cassandra 5.0은 JDK 17을 지원하며 ZGC(Z Garbage Collector)를 공식 옵션으로 사용할 수 있다.

# jvm17-server.options - Cassandra 5.0 JVM 설정
# 힙 메모리 (전체 메모리의 1/4, 최대 8GB 권장)
-Xms8G
-Xmx8G

# ZGC 사용 (Cassandra 5.0 권장)
-XX:+UseZGC
-XX:+ZGenerational

# GC 로깅
-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/cassandra/gc.log:utctime,pid,tags:filecount=32,filesize=64m

# Direct Memory (off-heap 메모리)
-XX:MaxDirectMemorySize=4G

# 대형 페이지 활용
-XX:+UseLargePages
-XX:LargePageSizeInBytes=2m

6.2 Memtable 및 CommitLog 설정

# cassandra.yaml 핵심 설정
memtable_heap_space: 2048 # MB, 기본값은 힙의 1/4
memtable_offheap_space: 2048
memtable_flush_writers: 4 # 플러시 스레드 수

# CommitLog 설정
commitlog_sync: periodic
commitlog_sync_period: 10000 # ms, batch 모드보다 성능 우선
commitlog_total_space: 8192 # MB
commitlog_segment_size: 32 # MB

# Compaction 동시성
concurrent_compactors: 4 # CPU 코어의 1/4 권장
compaction_throughput: 256 # MB/s, 0이면 무제한

# Read/Write 동시성
concurrent_reads: 32
concurrent_writes: 32
concurrent_counter_writes: 32

# 캐시 설정
row_cache_size: 0 # 대부분 비활성화 권장
key_cache_size_in_mb: 100
key_cache_save_period: 14400 # 4시간

# 네트워크 설정
native_transport_max_threads: 128
native_transport_max_frame_size: 16 # MB

6.3 필수 nodetool 명령어

# 클러스터 상태 확인
nodetool status

# 특정 테이블의 통계 확인
nodetool tablestats ecommerce.orders_by_user

# Compaction 통계
nodetool compactionstats

# 링 토큰 분포 확인
nodetool ring

# 수동 Repair 실행 (정기적으로 gc_grace_seconds 내에 수행 필요)
nodetool repair -pr ecommerce orders_by_user

# SSTable 수동 플러시
nodetool flush ecommerce orders_by_user

# 가비지 컬렉션 로그 확인
nodetool gcstats

# 스트리밍 진행 상황 (노드 추가/제거 시)
nodetool netstats

# 스냅샷 생성 (백업용)
nodetool snapshot -t backup_20260309 ecommerce

# Cassandra 5.0: SAI 인덱스 상태 확인
nodetool indexstats ecommerce.orders_by_user

7. Cassandra vs ScyllaDB 비교

ScyllaDB는 Cassandra와 CQL 호환성을 유지하면서 C++과 Seastar 프레임워크로 재구현한 데이터베이스다. JVM 오버헤드를 제거하고 shard-per-core 아키텍처를 채택하여 극단적인 성능 최적화를 달성한다.

비교 항목Apache Cassandra 5.0ScyllaDB Enterprise
구현 언어Java (JDK 17)C++ (Seastar 프레임워크)
아키텍처스레드 기반shard-per-core (코어당 독립 실행)
GC 영향JVM GC 일시 정지 존재GC 없음 (C++ 수동 메모리 관리)
P99 레이턴시10~50ms (워크로드에 따라)1~15ms (일관적으로 낮음)
노드 대비 성능기준 1x약 5~10x 더 높은 처리량
운영 비용더 많은 노드 필요적은 노드로 동일 처리량, 약 75% TCO 절감
CompactionUCS (5.0), STCS/LCS/TWCSIncremental Compaction Strategy (ICS)
벡터 검색SAI 기반 (5.0)지원 (ScyllaDB 6.x)
커뮤니티/생태계대규모, Apache 재단상대적으로 작음
학습 곡선광범위한 문서/자료Cassandra 경험 이전 가능
라이선스Apache 2.0 (완전 오픈소스)AGPL (Enterprise는 상용)
적합 환경범용, 대규모 커뮤니티 필요 시극단적 성능, 비용 최적화 필요 시

실제 벤치마크에서 ScyllaDB 4노드 클러스터가 Cassandra 40노드 클러스터와 동등한 처리량을 보여주는 사례가 보고되었다. 단, ScyllaDB의 AGPL 라이선스와 상대적으로 작은 커뮤니티는 도입 시 고려해야 할 사항이다.

8. 장애 시나리오와 복구 절차

8.1 시나리오 1: 단일 노드 다운

증상: 특정 노드가 응답하지 않으며, nodetool status에서 DN(Down/Normal) 상태로 표시된다.

복구 절차:

  1. 해당 노드의 시스템 로그와 Cassandra 로그를 확인한다
  2. 디스크 공간, 메모리, CPU 사용률을 점검한다
  3. OOM이 원인인 경우 힙 크기를 조정하고 재시작한다
  4. 하드웨어 장애인 경우 노드를 교체한다
# 1. 노드 상태 확인
nodetool status

# 2. 로그 확인
tail -500 /var/log/cassandra/system.log | grep -i "error\|exception\|oom"

# 3. 노드 재시작 후 데이터 정합성 확인
sudo systemctl restart cassandra

# 4. 재시작 후 repair 실행 (힌트 미전달 분량 복구)
nodetool repair -pr ecommerce

주의: RF=3, CL=QUORUM 환경에서는 단일 노드 장애 시에도 서비스 중단 없이 읽기/쓰기가 가능하다. 단, 장시간 다운 시 max_hint_window(기본 3시간)을 초과하면 hinted handoff가 동작하지 않으므로 반드시 repair를 수행해야 한다.

8.2 시나리오 2: 데이터 불일치(Inconsistency)

증상: 같은 쿼리에 대해 다른 결과가 반환되거나, CL=ALL로 읽을 때만 최신 데이터가 조회된다.

원인: repair가 장기간 수행되지 않았거나, hinted handoff 윈도우를 초과한 장애가 있었던 경우에 발생한다.

복구 절차:

  1. nodetool repair를 전체 키스페이스에 대해 실행한다
  2. 대규모 클러스터에서는 subrange repair를 사용하여 부하를 분산한다
  3. 운영 환경에서는 Reaper 같은 repair 자동화 도구를 도입한다
# 서브레인지 repair (대규모 클러스터용)
nodetool repair -pr -st -9223372036854775808 -et -4611686018427387904 ecommerce

# 또는 Cassandra Reaper 사용 (권장)
# Reaper API로 repair 스케줄 등록
curl -X POST "http://reaper:8080/repair_schedule" \
  -d "clusterName=production" \
  -d "keyspace=ecommerce" \
  -d "scheduleDaysBetween=7" \
  -d "intensity=0.5"

8.3 시나리오 3: 핫스팟 파티션

증상: 특정 노드의 CPU와 I/O가 비정상적으로 높으며, 다른 노드는 여유가 있다.

원인: 파티션 키 설계 오류로 인해 특정 파티션에 트래픽이 집중된다. 예를 들어 국가 코드를 파티션 키로 사용하면 한국('KR')에 대부분의 트래픽이 몰린다.

복구 절차:

  1. nodetool tablehistograms로 파티션 크기 분포를 확인한다
  2. 가장 큰 파티션을 식별하여 키 설계를 검토한다
  3. 복합 파티션 키를 사용하여 버킷팅을 적용한다
  4. 새 테이블을 생성하고 데이터를 마이그레이션한다
# 파티션 크기 히스토그램 확인
nodetool tablehistograms ecommerce.orders_by_user

# 가장 큰 파티션 확인
nodetool toppartitions ecommerce.orders_by_user 10 1000

9. 운영 시 주의사항 체크리스트

프로덕션 환경에서 Cassandra를 안정적으로 운영하기 위한 핵심 체크리스트를 정리한다.

클러스터 설계 단계:

  • RF는 최소 3 이상으로 설정하고, 멀티 DC 구성 시 각 DC에 3 이상의 레플리카를 배치한다
  • num_tokens는 16으로 설정하고 allocate_tokens_for_local_replication_factor를 활성화한다
  • 데이터 디렉토리와 CommitLog 디렉토리를 물리적으로 분리된 디스크에 배치한다
  • NTP 동기화를 반드시 구성한다 (노드 간 시계 차이가 데이터 불일치를 유발)

데이터 모델링 단계:

  • 쿼리 패턴을 먼저 정의한 뒤 테이블을 설계한다
  • 파티션 크기를 50MB 이하로 유지하도록 버킷팅 전략을 적용한다
  • ALLOW FILTERING은 절대 프로덕션에서 사용하지 않는다
  • TTL과 gc_grace_seconds의 관계를 이해하고 적절히 설정한다

운영 단계:

  • gc_grace_seconds(기본 10일) 이내에 반드시 repair를 완료한다. 이를 초과하면 삭제된 데이터가 다시 부활(zombie data)할 수 있다
  • Compaction 대기 중인 SSTable 수를 모니터링한다 (임계치: 32개 이상이면 경고)
  • 힙 사용률과 GC 일시 정지 시간을 지속적으로 모니터링한다
  • 디스크 사용률은 50% 이하로 유지한다 (Compaction 시 임시 공간이 필요)
  • 노드 추가/제거는 한 번에 하나씩 수행하고, 스트리밍이 완료될 때까지 대기한다
  • 스키마 변경은 클러스터 전체가 정상 상태일 때만 수행한다

백업 및 복구:

  • nodetool snapshot으로 정기적인 스냅샷을 생성한다
  • 증분 백업(incremental_backups: true)을 활성화하여 마지막 스냅샷 이후의 변경 사항을 보존한다
  • 복구 테스트를 분기별로 수행하여 백업의 유효성을 검증한다

모니터링 필수 메트릭:

  • Read/Write Latency (P99)
  • Compaction Pending Tasks
  • SSTable Count per Table
  • Heap Usage / GC Pause Duration
  • Dropped Messages (Mutation, Read, Counter)
  • Disk Usage per Node
  • Hinted Handoff Queue Size
  • Tombstone 스캔 수 (높으면 모델링 검토 필요)

참고자료

  1. Apache Cassandra 5.0 공식 릴리스 공지 - Cassandra 5.0의 주요 기능과 릴리스 상세 정보
  2. Apache Cassandra 공식 문서 - New Features - SAI, 벡터 검색, Trie SSTable, UCS 등 5.0 신기능 공식 가이드
  3. Unified Compaction Strategy 공식 문서 - UCS의 설계 원리와 상세 설정 가이드
  4. Apache Cassandra 5.0 UCS 블로그 포스트 - UCS 도입 배경과 기존 전략 대비 이점 분석
  5. ScyllaDB vs Apache Cassandra 벤치마크 - 40노드 Cassandra vs 4노드 ScyllaDB 상세 성능 비교
  6. ScyllaDB 공식 비교 페이지 - ScyllaDB와 Cassandra의 아키텍처 및 성능 차이 분석
  7. DataStax Compaction 전략 가이드 - STCS, LCS, TWCS 전략 선택 가이드
  8. Cassandra Deep Dive: Compaction Strategies - Compaction 전략별 내부 동작 원리와 트레이드오프 분석