본문으로 건너뛰기
clickhouse검색
운영과 트러블슈팅

TTL, 디스크 티어링, 데이터 보존 (S3 콜드 스토리지, 삭제와 롤업)

테이블 TTL 과 열 TTL 로 오래된 데이터를 지우거나 재압축하거나 롤업하는 문법, 스토리지 정책으로 로컬 SSD 에서 S3 로 옮기는 티어링, TTL 이 실제로 언제 적용되는지와 ttl_only_drop_parts 를 정리합니다.

TTL스토리지 정책S3티어링데이터 보존2026-10 기준, 약 11분상태 표시 설명

TTL 이 하는 일

TTL 은 "이 시점이 지나면 이 행(또는 열, 파트)에 무엇을 하라" 는 규칙입니다. 할 수 있는 일은 네 가지입니다.

동작문법쓰는 곳
삭제TTL ts + INTERVAL 90 DAY (기본 DELETE)로그, 이벤트 보존 기간
다른 디스크나 볼륨으로 이동TTL ts + INTERVAL 7 DAY TO VOLUME 'cold'핫은 SSD, 콜드는 S3
재압축TTL ts + INTERVAL 30 DAY RECOMPRESS CODEC(ZSTD(9))오래된 데이터는 더 세게 압축
롤업TTL ts + INTERVAL 30 DAY GROUP BY ... SET cnt = sum(cnt)상세 데이터를 집계로 바꾸기

TTL 식은 Date 또는 DateTime 을 돌려주는 식이면 됩니다. 열 하나에 걸면 열 TTL(지난 값을 기본값으로 바꿈), 테이블에 걸면 테이블 TTL 입니다.

CREATE TABLE logs.events
(
    ts DateTime,
    service LowCardinality(String),
    level LowCardinality(String),
    message String,
    raw String TTL ts + INTERVAL 7 DAY      -- 원문은 일주일만, 그 뒤엔 빈 문자열
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (service, ts)
TTL ts + INTERVAL 30 DAY TO VOLUME 'cold',
    ts + INTERVAL 180 DAY DELETE
SETTINGS storage_policy = 'tiered', ttl_only_drop_parts = 1;

참고 열 TTL 은 값을 기본값으로 바꾸는 것이라 공간을 거의 돌려주지만 열이 사라지는 것은 아닙니다. 쿼리에서는 그 열이 빈 값으로 보입니다.

TTL 은 언제 실제로 적용되나

TTL 은 INSERT 시점이 아니라 머지 시점에 적용됩니다. 머지가 없으면 지나간 데이터도 그대로 남아 있습니다. ClickHouse 는 TTL 전용 머지를 merge_with_ttl_timeout(기본 14400초, 4시간)마다 한 번씩만 시도합니다. "TTL 을 걸었는데 안 지워진다" 는 질문은 대부분 이 4시간과 아래 두 가지 때문입니다.

  1. 파트 전체가 만료되지 않으면 파트를 다시 써야 하므로 비쌉니다. ttl_only_drop_parts = 1 을 켜면 파트 안의 모든 행이 만료된 파트만 통째로 지우고, 부분 만료 파트는 건드리지 않습니다. 파티션과 TTL 단위를 맞추면(일별 파티션에 N 일 TTL) 거의 모든 삭제가 파트 드롭으로 끝나 비용이 0 에 가깝습니다.
  2. 이미 있는 데이터에 TTL 을 새로 걸거나 바꾸면 ALTER TABLE ... MATERIALIZE TTL 을 돌려야 과거 파트에도 적용됩니다. materialize_ttl_after_modify(기본 1)가 켜져 있으면 ALTER TABLE ... MODIFY TTL 때 자동으로 돌아갑니다. 큰 테이블에서는 뮤테이션 한 번과 같은 비용입니다.

지금 돌고 있는 TTL 머지는 system.merges 의 merge_type 이 TTLDelete, TTLRecompress 로 보입니다. 파트별 TTL 상태는 system.parts 에서 봅니다.

SELECT partition, name, rows, formatReadableSize(bytes_on_disk) AS size,
       delete_ttl_info_min, delete_ttl_info_max, disk_name
FROM system.parts
WHERE table = 'events' AND active
ORDER BY partition;

delete_ttl_info_max 가 지금보다 과거면 그 파트는 통째로 지워질 차례입니다. 급하면 OPTIMIZE TABLE logs.events FINAL 또는 파티션 지정으로 머지를 강제할 수 있지만, 파티션 드롭이 훨씬 싸므로 보존 기간이 파티션 단위와 맞으면 그냥 ALTER TABLE logs.events DROP PARTITION '202604' 로 지우는 쪽이 낫습니다.

스토리지 정책: 핫은 SSD, 콜드는 S3

디스크와 볼륨과 정책은 config.d 에 둡니다.

<clickhouse>
    <storage_configuration>
        <disks>
            <s3_cold>
                <type>s3</type>
                <endpoint>https://my-bucket.s3.ap-northeast-2.amazonaws.com/ch/</endpoint>
                <use_environment_credentials>true</use_environment_credentials>
                <metadata_path>/var/lib/clickhouse/disks/s3_cold/</metadata_path>
            </s3_cold>
            <s3_cold_cache>
                <type>cache</type>
                <disk>s3_cold</disk>
                <path>/var/lib/clickhouse/disks/s3_cold_cache/</path>
                <max_size>100Gi</max_size>
            </s3_cold_cache>
        </disks>
        <policies>
            <tiered>
                <volumes>
                    <hot>
                        <disk>default</disk>
                        <max_data_part_size_bytes>10737418240</max_data_part_size_bytes>
                    </hot>
                    <cold>
                        <disk>s3_cold_cache</disk>
                        <prefer_not_to_merge>false</prefer_not_to_merge>
                    </cold>
                </volumes>
                <move_factor>0.1</move_factor>
            </tiered>
        </policies>
    </storage_configuration>
</clickhouse>
  • 볼륨은 순서가 있습니다. 새 파트는 첫 볼륨(hot)에 쓰이고, TTL TO VOLUME 이나 move_factor(남은 공간이 10% 아래면 다음 볼륨으로 밀어냄)로 내려갑니다.
  • cache 타입 디스크로 S3 디스크를 감싸면 읽은 데이터를 로컬에 캐시합니다. 콜드 데이터를 반복 조회할 때 S3 요청 비용과 지연을 크게 줄입니다.
  • 정책을 바꾸는 것은 테이블 SETTINGS storage_policy 를 바꾸거나 새 테이블로 옮기는 식입니다. 기존 정책에 볼륨을 추가하는 것은 되지만 빼는 것은 안 됩니다.
SELECT policy_name, volume_name, volume_priority, disks, max_data_part_size, move_factor
FROM system.storage_policies;

SELECT name, path, formatReadableSize(free_space) AS free,
       formatReadableSize(total_space) AS total, type
FROM system.disks;

-- 파트를 손으로 옮기기
ALTER TABLE logs.events MOVE PARTITION '202605' TO VOLUME 'cold';

주의 S3 디스크 위의 MergeTree 는 복제본마다 데이터를 따로 올립니다. 복제본 3 개면 S3 에 3 벌이 있습니다. 한 벌만 두고 공유하는 allow_remote_fs_zero_copy_replication 은 Exp 이고 운영에 권장되지 않습니다. 이 문제를 제대로 푼 것이 Cloud 의 SharedMergeTree 입니다 (ClickHouse Cloud 개요).

롤업 TTL: 상세는 짧게, 집계는 길게

30 일이 지난 행을 시간 단위 집계 한 줄로 접는 예입니다. GROUP BY 키는 정렬 키의 앞부분이어야 하고, SET 에 없는 열은 그룹의 임의 행 값이 남습니다.

CREATE TABLE metrics.requests
(
    ts DateTime,
    service LowCardinality(String),
    status UInt16,
    cnt UInt64,
    latency_sum Float64
)
ENGINE = MergeTree
ORDER BY (service, toStartOfHour(ts), status, ts)
TTL ts + INTERVAL 30 DAY
    GROUP BY service, toStartOfHour(ts), status
    SET cnt = sum(cnt), latency_sum = sum(latency_sum), ts = min(ts);

실무에서는 롤업 TTL 보다 머티리얼라이즈드 뷰로 집계 테이블을 따로 두고 상세 테이블에 짧은 TTL 을 거는 쪽이 운영하기 편합니다. 롤업 TTL 은 머지 시점에 조용히 일어나서 "어느 행이 집계됐는지" 를 추적하기 어렵습니다.

조건부 삭제와 여러 TTL

ALTER TABLE logs.events MODIFY TTL
    ts + INTERVAL 7 DAY DELETE WHERE level = 'DEBUG',
    ts + INTERVAL 90 DAY DELETE WHERE level IN ('INFO', 'WARN'),
    ts + INTERVAL 365 DAY;

WHERE 를 붙이면 조건에 맞는 행만 지웁니다. 이 경우 파트 통째 드롭이 어려워져 재쓰기 머지가 일어나니 비용을 생각해야 합니다. 보존 기간이 등급별로 다르다면 테이블을 나누는 것도 방법입니다.

데이터 보존 설계 체크리스트

  1. 보존 기간을 파티션 단위의 배수로 둡니다 (일별 파티션 + 30 일, 월별 파티션 + 12 개월). ttl_only_drop_parts = 1.
  2. 삭제 TTL 과 이동 TTL 을 같이 쓸 때는 이동이 먼저, 삭제가 나중이어야 합니다 (7 일 뒤 S3, 180 일 뒤 삭제).
  3. S3 콜드 데이터를 쿼리할 일이 있으면 cache 디스크를 꼭 씌웁니다. 없으면 콜드 조회가 매번 S3 를 두드립니다.
  4. 규정상 삭제 증빙이 필요하면 system.part_log 의 RemovePart 이벤트와 시간을 남겨 둡니다.
  5. 백업과 TTL 은 별개입니다. TTL 로 지운 데이터는 백업에서만 돌아옵니다 (백업과 복원, 업그레이드).
  6. 특정 사용자 데이터만 지우는 요청(개인정보 삭제)은 TTL 이 아니라 경량 삭제나 파티션 설계로 풉니다 (삭제와 갱신).

Cloud 에서는

ClickHouse Cloud 는 모든 데이터가 오브젝트 스토리지에 있고 로컬 SSD 는 캐시이므로 티어링 정책을 직접 짤 필요가 없습니다. TTL 의 삭제, 재압축, 롤업은 똑같이 동작합니다. TO VOLUME 과 TO DISK 는 Cloud 에서 의미가 없습니다. 비용은 압축 후 저장 용량 기준이라 TTL 삭제가 곧 비용 절감입니다 (비용 구조와 절감).

공식 문서

이 문서에서