왜 컴팩션이 읽기 성능을 좌우하는가
ScyllaDB(및 Cassandra 계열)는 쓰기를 memtable에 모은 뒤 immutable한 SSTable로 flush한다. 같은 파티션 키의 데이터가 여러 SSTable에 흩어지면, 한 번의 읽기가 여러 파일을 뒤져야 한다. 이것이 읽기 증폭(read amplification)이다. 하나의 row를 조립하려고 5개 SSTable을 열면 디스크 I/O와 지연이 그만큼 늘어난다. 컴팩션은 이 흩어진 SSTable을 병합해 파일 수를 줄이고 tombstone을 정리하는 백그라운드 작업이며, 어떤 전략을 쓰느냐가 읽기 지연의 상한을 결정한다.
세 가지 읽기 증폭
튜닝 전에 무엇을 줄이는지 구분해야 한다.
- 읽기 증폭: 한 쿼리가 접근하는 SSTable 수. 낮을수록 좋다.
- 쓰기 증폭: 같은 데이터가 컴팩션으로 재작성되는 횟수. 낮을수록 디스크 수명·대역폭에 유리하다.
- 공간 증폭: 실제 유효 데이터 대비 점유 디스크. 낮을수록 좋다.
세 가지를 동시에 최소화할 수는 없다. 전략 선택은 이 트레이드오프 중 무엇을 포기할지 정하는 일이다.
전략별 특성 비교
| 전략 | 읽기 증폭 | 쓰기 증폭 | 공간 증폭 | 적합 워크로드 |
|---|---|---|---|---|
| STCS (Size-Tiered) | 높음 | 낮음 | 높음 | 쓰기 위주, append성 |
| LCS (Leveled) | 낮음 | 높음 | 낮음 | 읽기 위주, 갱신 잦음 |
| TWCS (Time-Window) | 낮음(범위 쿼리) | 낮음 | 중간 | TTL 시계열 |
| ICS (Incremental) | 낮음 | 낮음 | 낮음 | Scylla Enterprise 범용 |
기본값 STCS는 쓰기 부담이 적지만, 같은 row를 반복 갱신하면 조각이 여러 tier에 남아 읽기 증폭이 커진다. 읽기 지연이 문제라면 LCS나 TWCS가 우선 후보다.
시계열에는 TWCS
센서·로그처럼 시간 순으로 쌓이고 TTL로 만료되는 데이터는 TWCS가 정석이다. 시간 창(window) 단위로 SSTable을 묶어, 만료된 창은 컴팩션 없이 파일째 삭제한다. 창 크기는 보존 기간 / 20~30개 정도가 되도록 잡는 것이 경험칙이다. 30일 보존이면 하루 창이 적당하다.
CREATE TABLE metrics.sensor_data (
sensor_id text,
ts timestamp,
value double,
PRIMARY KEY (sensor_id, ts)
) WITH CLUSTERING ORDER BY (ts DESC)
AND default_time_to_live = 2592000
AND compaction = {
'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'DAYS',
'compaction_window_size': 1
};
주의: TWCS에서 과거 타임스탬프를 뒤늦게 쓰면(out-of-order) 이미 닫힌 창에 새 SSTable이 생겨 삭제가 지연되고 읽기 증폭이 되살아난다. 백필은 별도 테이블로 분리하라.
갱신 잦은 조회 테이블에는 LCS
사용자 프로필처럼 같은 row를 자주 덮어쓰고 point read가 많은 테이블은 LCS가 유리하다. LCS는 레벨 구조로 SSTable을 정렬해, 대부분의 읽기가 레벨당 1개꼴로 접근하도록 상한을 건다. 대신 쓰기 증폭이 커서 디스크 대역폭이 넉넉해야 한다.
ALTER TABLE app.user_profile WITH compaction = {
'class': 'LeveledCompactionStrategy',
'sstable_size_in_mb': 160
};
전환 직후에는 재컴팩션으로 I/O가 튄다. 트래픽이 낮은 시간대에 적용하고, 아래처럼 SSTable당 실제 접근 분포를 확인해 효과를 검증한다.
#!/usr/bin/env bash
# 특정 키가 몇 개 SSTable에 걸쳐 있는지 확인
nodetool getsstables app user_profile 'user-42' | wc -l
# 테이블별 읽기 증폭 히스토그램 (SSTables per Read 열 확인)
nodetool tablehistograms app.user_profile
Tombstone과 공간 증폭 함정
DELETE와 TTL 만료는 즉시 지우지 않고 tombstone을 남긴다. tombstone이 컴팩션으로 정리되기 전까지 읽기는 죽은 데이터까지 스캔한다. 삭제가 잦은데 컴팩션이 못 따라가면 읽기 증폭과 공간 증폭이 동시에 악화된다. gc_grace_seconds를 워크로드에 맞게 조정하되(기본 10일), 힌트·리페어 주기보다 짧게 잡아 좀비 데이터가 부활하지 않게 한다. 단일 파티션에 tombstone이 수천 개 쌓이면 쿼리가 경고 후 중단되므로, 넓은 범위 삭제 대신 파티션 설계로 회피하는 편이 낫다.
운영 체크리스트
- 전략 변경은 테이블 단위로 가능하다. 워크로드가 다르면 테이블마다 다른 전략을 쓴다.
- 변경 후
nodetool tablehistograms의 SSTables per Read p99가 목표치(LCS는 2~3 이하)로 떨어지는지 반드시 확인한다. - 컴팩션이 밀리는지
nodetool compactionstats의 pending 개수로 감시한다. 지속적으로 쌓이면 처리량 한계 신호다. - Scylla Enterprise라면 STCS/LCS 대신 ICS를 기본으로 검토한다. STCS의 낮은 쓰기 증폭과 LCS 수준의 낮은 공간 증폭을 함께 노린 전략이다.
결론은 단순하다. 읽기 지연을 재고, 그 테이블의 접근 패턴(시계열·갱신·append)에 맞는 전략을 골라, 변경 전후의 SSTables per Read를 숫자로 비교하는 것. 컴팩션 튜닝은 감이 아니라 히스토그램으로 검증하는 작업이다.