ClickHouse 디스크가 차면 머지가 막히는 이유
ClickHouse에서 디스크 80%는 단순한 용량 경보가 아닙니다.
MergeTree는 작은 part를 읽어 새 part를 쓴 뒤에야 원본을 정리합니다.
즉 데이터 1TB를 저장하고 있다고 해서 1TB짜리 볼륨으로 운영할 수 없습니다.
여유가 사라지면 먼저 머지가 후보를 잡지 못하고, 작은 part가 쌓이고, 마지막에는 INSERT가 느려지거나 TOO_MANY_PARTS로 거부됩니다.
머지는 제자리 덮어쓰기가 아니다
part A와 B를 합칠 때 ClickHouse는 기존 part를 바로 지우지 않습니다.
원본 part A ─┐
├─ 읽기·정렬·병합 ─→ 새 part C
원본 part B ─┘
새 part C commit 성공
↓
원본 A·B 비활성화 후 정리진행 중에는 원본과 결과가 함께 존재합니다.
압축률이 비슷하다면 결과 part 크기는 원본 합계에 가까우므로 대형 머지 하나가 원본 합계만큼의 추가 공간을 요구할 수 있습니다.
“머지에 2배 공간이 필요하다”는 말은 전체 볼륨을 언제나 두 배로 잡으라는 공식이 아닙니다.
선택된 원본 part와 새 결과 part가 동시에 존재하는 구간을 설명하는 근사입니다.
디스크 부족은 연쇄 장애를 만든다
- 큰 머지가 남은 공간을 예약합니다.
- 다른 머지가 실행할 공간을 찾지 못합니다.
- 새 INSERT가 작은 part를 계속 만듭니다.
- 파티션별 active part 수가 증가합니다.
- ClickHouse가 INSERT를 지연시키거나 거부합니다.
ClickHouse의 OPTIMIZE FINAL 설명 은 머지가 원본을 읽고 새 part를 쓰며 CPU, 메모리, I/O와 디스크 공간을 소비한다고 설명합니다.
OPTIMIZE TABLE ... FINAL은 자동 머지가 피하던 큰 작업까지 강제할 수 있어 디스크가 빠듯한 상황의 응급 처치로 쓰면 위험합니다.
먼저 part 수와 크기를 본다
SELECT
database,
table,
partition,
count() AS active_parts,
formatReadableSize(sum(bytes_on_disk)) AS bytes_on_disk,
formatReadableSize(max(bytes_on_disk)) AS largest_part
FROM system.parts
WHERE active
GROUP BY database, table, partition
ORDER BY active_parts DESC
LIMIT 100;part 수가 많은 테이블만 보는 것보다 partition까지 묶어야 합니다.
서로 다른 partition의 part는 합쳐지지 않기 때문입니다.
고카디널리티 partition key를 쓰면 머지 후보가 여러 디렉터리에 흩어져 part 수가 잘 줄지 않습니다.
ClickHouse의 일반적인 실수 정리 는 작은 INSERT와 과도한 partition cardinality를 Too many parts의 대표 원인으로 꼽습니다.
진행 중인 머지를 본다
SELECT
database,
table,
elapsed,
progress,
num_parts,
formatReadableSize(total_size_bytes_compressed) AS source_size,
formatReadableSize(memory_usage) AS memory,
result_part_name
FROM system.merges
ORDER BY elapsed DESC;오래 걸린 머지 하나만으로 장애라고 단정하지 않습니다.
대형 part를 합치는 작업은 원래 오래 걸릴 수 있습니다.
문제는 다음 신호가 함께 나타날 때입니다.
system.merges의 진행률이 장시간 거의 변하지 않는다.- 같은 partition의 active part 수가 계속 증가한다.
- 디스크 free space가 줄고 예약 공간이 커진다.
- INSERT 지연이나
TOO_MANY_PARTS오류가 나타난다.
디스크와 예약 공간을 같이 본다
SELECT
name,
path,
formatReadableSize(free_space) AS free,
formatReadableSize(total_space) AS total,
round(100 * (total_space - free_space) / total_space, 1) AS used_pct,
formatReadableSize(keep_free_space) AS keep_free
FROM system.disks;시스템 메트릭 이름은 ClickHouse 버전에 따라 달라질 수 있으므로 실제 system.metrics에서 예약 관련 항목을 찾습니다.
SELECT metric, value, description
FROM system.metrics
WHERE metric ILIKE '%Disk%'
OR metric ILIKE '%Merge%'
ORDER BY metric;대시보드에 고정된 메트릭 이름을 복사하기 전에 해당 버전에서 값이 존재하는지 확인하는 이유입니다.
임계값은 엔진 제한과 운영 예시를 구분한다
70% 경고 / 80% 조치 / 85% 상한 같은 값은 운영 정책의 예시입니다.
ClickHouse 엔진의 보편적인 하드 리밋이 아닙니다.
워크로드의 최대 머지 크기, 볼륨 확장 시간, 하루 증가량에 따라 더 일찍 개입해야 할 수 있습니다.
반대로 active parts 300도 모든 테이블의 엔진 제한으로 고정된 값이 아닙니다.
실효 제한은 parts_to_delay_insert, parts_to_throw_insert, max_parts_in_total 같은 설정과 partition별 상태에 영향을 받습니다.
SELECT name, value, changed
FROM system.merge_tree_settings
WHERE name IN (
'parts_to_delay_insert',
'parts_to_throw_insert',
'max_parts_in_total',
'max_bytes_to_merge_at_max_space_in_pool',
'number_of_free_entries_in_pool_to_lower_max_size_of_merge'
)
ORDER BY name;제한을 올리는 것은 part 생성 원인을 없애지 않습니다.
오히려 쿼리와 기동 시간에 더 많은 파일과 인덱스를 읽게 만들 수 있습니다.
응급 대응 순서
1. 작은 INSERT를 줄인다
새 part 생성 속도를 먼저 낮춥니다.
클라이언트 배치 크기를 키우거나 async insert를 사용하되, 성공 응답 시점과 dedup 설정을 함께 검토합니다.
2. 불필요한 대형 작업을 멈춘다
예약된 OPTIMIZE ... FINAL, 대형 mutation, TTL materialize가 문제를 키우고 있는지 확인합니다.
실행 중인 쿼리를 취소하기 전에는 query id, 대상 테이블, 재실행 필요 여부를 기록합니다.
3. 보존 정책으로 통째로 지울 수 있는 part를 찾는다
partition 단위 DROP이나 ttl_only_drop_parts=1로 만료 part를 통째로 제거할 수 있으면 행 단위 mutation보다 I/O가 적습니다.
삭제 대상과 백업 조건을 확인한 뒤 실행해야 합니다.
4. 볼륨을 확장한다
EBS와 CSI가 온라인 확장을 지원하고 Altinity CHI가 PVC를 직접 관리하도록 구성했다면 재시작 없이 늘릴 수 있습니다.
kubectl -n clickhouse patch pvc data-example-0-0-0 \
--type merge \
-p '{"spec":{"resources":{"requests":{"storage":"2Ti"}}}}'위 이름은 placeholder입니다.
패치 전에 StorageClass의 allowVolumeExpansion, PVC 소유자, operator의 storageManagement.provisioner를 확인해야 합니다.
성공 판정은 PVC 요청값 변경이 아니라 filesystem이 실제로 확장되고 system.disks.free_space가 늘어난 것입니다.
AWS EBS 수정 횟수와 상태 제약은 시점에 따라 바뀌므로 Elastic Volumes 공식 문서 를 실행 시점에 확인합니다.
머지 풀을 먼저 키우면 안 되는 경우
background_pool_size나 머지 동시성을 올리면 backlog를 더 빨리 처리할 수 있습니다.
하지만 이미 디스크 처리량이 포화됐다면 작업을 더 동시에 실행해 쿼리와 INSERT를 함께 느리게 만듭니다.
설정 변경 전에 최소한 다음을 같은 시간축으로 봅니다.
part 생성률
part 감소률
system.merges 동시 수와 처리량
디스크 read/write throughput
쿼리 지연
replication queue머지가 CPU를 기다리면 동시성 조정이 후보가 될 수 있습니다.
머지가 디스크를 기다리면 배치와 공간부터 고치는 편이 낫습니다.
예방 계산
볼륨 요청량은 단순 보관량에 40%를 더하는 한 줄로 끝내지 않습니다.
정상 상태 데이터
+ 하루 최대 증가량 × 볼륨 확장 리드타임
+ 동시에 실행할 수 있는 가장 큰 머지 결과
+ 로그·캐시·메타데이터
+ 운영 오차
= 최소 요청 용량데이터 × 1.4는 한 가지 산정 방법이고 “전체 볼륨의 40%를 항상 비워둔다”와 같은 문장이 아닙니다.
전자는 데이터량에 곱한 결과이고 후자는 볼륨 사용률 60%를 뜻하므로 숫자가 다릅니다.
완료 판정
디스크를 늘렸다고 끝내지 않습니다.
- free space가 목표 범위로 회복됐다.
- active part 증가가 멈추고 감소한다.
system.merges가 다시 진행한다.- INSERT 지연과 오류가 정상화됐다.
- replication queue가 줄어든다.
- 임시로 바꾼 설정을 원래 정책으로 되돌렸다.
결론 : 디스크 부족은 저장 실패 전에 머지 backlog와 part 폭증으로 나타나므로, 공간·part 생성률·머지 진행률을 한 흐름으로 봐야 합니다.
설정과 AWS 제약 확인 시점은 2026-09-15입니다.