압축과 코덱 (LZ4, ZSTD, Delta, DoubleDelta, Gorilla, T64)
ClickHouse가 열 데이터를 어떻게 압축하는지, 범용 코덱(LZ4, ZSTD)과 특화 코덱(Delta, DoubleDelta, Gorilla, T64, FPC)을 열 특성에 따라 어떻게 조합하는지, 압축률을 측정하고 바꾸는 방법을 정리합니다.
압축이 곧 속도
ClickHouse에서 압축은 디스크 절약만이 아니라 읽기 속도입니다. 디스크와 네트워크에서 읽는 바이트가 줄고, 캐시에 더 많이 담기기 때문입니다. 분석 쿼리는 대부분 I/O 또는 메모리 대역폭에 묶이므로, 압축률 2배는 체감상 스캔 2배 빨라짐에 가깝습니다. 반면 CPU가 병목인 쿼리에서는 ZSTD 높은 레벨이 오히려 느려집니다. 데이터 특성과 쿼리 패턴을 보고 고릅니다.
압축은 그래뉼 단위 블록으로 일어납니다. 열 파일 <열>.bin은 압축 블록(기본 최대 1MB 압축 전)의 연속이고, 마크 파일이 블록 위치를 가리킵니다. 정렬 키 순서로 비슷한 값이 몰릴수록 같은 블록 안에서 반복이 늘어 압축률이 오릅니다. 그래서 정렬 키 뒤쪽 열은 인덱스보다 압축에 기여합니다(정렬 키, 기본 키, 파티션).
범용 코덱
| 코덱 | 특징 | 쓰는 때 |
|---|---|---|
LZ4 | 빠른 압축과 해제. 기본값 (셀프 호스팅) | CPU가 아쉬운 서버, 자주 읽는 핫 데이터 |
ZSTD(level) | 압축률 높음. 레벨 1~22, 기본 1 | 대부분의 경우 ZSTD(1)이 균형. Cloud 기본값 |
LZ4HC(level) | LZ4보다 압축률 높고 느림 | 거의 ZSTD로 대체 |
NONE | 압축 없음 | 이미 압축된 바이너리, 난수 |
- ZSTD는 레벨 3 이상에서 압축률 이득이 완만해지고 쓰기 CPU가 급격히 늘어납니다. 머지가 자주 다시 압축하므로
ZSTD(1)에서ZSTD(3)사이가 현실적입니다. - 서버 기본 코덱은
config.xml의<compression>에서 바꿀 수 있고, 테이블 열 단위CODEC이 우선합니다.
<compression>
<case>
<min_part_size>10000000000</min_part_size>
<min_part_size_ratio>0.01</min_part_size_ratio>
<method>zstd</method>
<level>1</level>
</case>
</compression>특화 코덱
특화 코덱은 데이터를 변환만 하고 실제 압축은 뒤에 붙는 범용 코덱이 합니다. 그래서 CODEC(Delta, ZSTD)처럼 체인으로 씁니다.
| 코덱 | 동작 | 맞는 열 |
|---|---|---|
Delta(N) | 이전 값과의 차이를 저장 | 단조 증가 정수, 타임스탬프, 시퀀스 번호 |
DoubleDelta | 차이의 차이를 저장 | 일정 간격 타임스탬프, 카운터 |
Gorilla | 이전 값과 XOR | 천천히 변하는 Float 게이지(온도, CPU 사용률) |
FPC | Float 예측 기반 | Float64 시계열, Gorilla와 비교해 선택 |
T64 | 블록 안 값들의 공통 상위 비트를 잘라냄 | 범위가 좁은 정수(상태 코드, 작은 카운트) |
GCD | 최대공약수로 나눔 | 10, 1000 단위처럼 공약수가 있는 정수 |
ZSTD_QAT, DEFLATE_QAT | 하드웨어 가속 | 특정 Intel 서버 |
CREATE TABLE metrics
(
ts DateTime CODEC(DoubleDelta, ZSTD(1)),
series_id UInt32 CODEC(T64, ZSTD(1)),
value Float64 CODEC(Gorilla, ZSTD(1)),
seq UInt64 CODEC(Delta, ZSTD(1)),
payload String CODEC(ZSTD(3))
)
ENGINE = MergeTree ORDER BY (series_id, ts);팁 타임스탬프 열은
DoubleDelta가 거의 항상 이득이고, 정렬 키의 첫 열처럼 같은 값이 길게 반복되는 열은 범용 코덱만으로 충분합니다. Float 게이지는 Gorilla, 랜덤에 가까운 Float(금액, 좌표)는 그냥 ZSTD가 낫습니다.
측정하고 결정하기
감으로 고르지 말고 같은 데이터로 코덱만 바꿔 비교합니다. system.parts_columns에 열별 압축 전후 크기가 있습니다.
-- 열별 압축률
SELECT column, type, compression_codec,
formatReadableSize(sum(column_data_compressed_bytes)) AS compressed,
round(sum(column_data_uncompressed_bytes) / sum(column_data_compressed_bytes), 1) AS ratio
FROM system.parts_columns
WHERE active AND table = 'metrics'
GROUP BY column, type, compression_codec
ORDER BY sum(column_data_compressed_bytes) DESC;비교 실험 절차입니다.
- 테이블을
CREATE TABLE t_zstd AS t ENGINE = MergeTree ORDER BY ...로 몇 벌 만들고 열 코덱만 다르게 선언합니다. - 같은 파티션 한 달치를
INSERT ... SELECT로 넣습니다. OPTIMIZE TABLE ... FINAL로 머지를 끝낸 뒤 압축 크기를 비교합니다. 머지 전 작은 파트는 압축률이 낮게 나옵니다.- 대표 쿼리 5개의 실행 시간과
system.query_log의read_bytes를 비교합니다.
-- 코덱 변경. 뮤테이션이므로 큰 테이블은 파티션별로 또는 머지에 맡김
ALTER TABLE metrics MODIFY COLUMN value Float64 CODEC(Gorilla, ZSTD(1));
-- 기존 파트까지 바로 다시 압축하려면
ALTER TABLE metrics MATERIALIZE COLUMN value; -- 또는 OPTIMIZE ... FINAL코덱 변경 ALTER는 메타데이터만 바꾸고, 기존 파트는 다음 머지 때 새 코덱으로 다시 압축됩니다. 급하지 않으면 머지에 맡기는 것이 서버에 부담이 적습니다.
예상 압축률 감
| 데이터 | 전형적 압축률 (압축 전 / 후) |
|---|---|
| 웹 로그, 애플리케이션 로그 (정렬 키 잘 잡힘) | 10배에서 30배 |
| 시계열 메트릭 (DoubleDelta, Gorilla) | 20배에서 100배 |
| 트랜잭션 테이블 (금액, ID 많음) | 3배에서 8배 |
| 텍스트 본문, 자유 입력 | 3배에서 6배 |
| UUID, 해시, 난수 | 1배에서 1.5배 (압축 안 됨) |
압축이 안 되는 열은 설계로 줄입니다. UUID 문자열은 UUID 타입으로(36바이트에서 16바이트), 해시는 UInt64로, 같은 값이 반복되는 문자열은 LowCardinality로 바꾸면 코덱보다 효과가 큽니다. 타입 선택은 데이터 타입 고르기.
네트워크와 클라이언트 압축
- 서버와 클라이언트 사이 Native 프로토콜은 기본으로 LZ4 압축을 씁니다(
network_compression_method). ZSTD로 바꾸면 WAN 구간에서 전송량이 줄지만 CPU를 더 씁니다. - HTTP 인터페이스는
enable_http_compression = 1과 요청 헤더Accept-Encoding: zstd(또는 gzip, br, lz4)로 응답을 압축합니다. 대용량 결과를 받는 ETL이라면 켜 두세요. - INSERT 요청 본문도
Content-Encoding으로 압축해 보낼 수 있습니다. 클라이언트와 드라이버 참고.
오브젝트 스토리지와 압축
S3 기반 디스크나 ClickHouse Cloud에서는 저장 비용이 압축 후 바이트로 매겨지고, 읽기는 요청 수와 바이트에 비례합니다. 압축률이 높을수록 스토리지 비용과 읽기 지연이 함께 줄어 효과가 두 배입니다. 반대로 압축이 안 되는 열(난수 UUID)은 Cloud 요금에 그대로 반영되므로 타입 정리가 비용 절감의 첫 단계입니다. 비용 구조와 절감에서 이어집니다.
체크리스트
- 서버 또는 테이블 기본 코덱이
ZSTD(1)인지 확인했다 (셀프 호스팅 기본은 LZ4). - 타임스탬프와 단조 증가 열은
Delta또는DoubleDelta를 앞에 붙였다. - Float 게이지는 Gorilla 또는 FPC를 실측으로 골랐다.
- 압축률 2배 이하인 열은 타입(UUID, UInt, LowCardinality)으로 먼저 줄였다.
- 코덱 변경 후 머지가 끝난 뒤
system.parts_columns로 다시 측정했다.