ClickStack의 TTL
Time-to-Live(TTL)은 ClickStack에서 효율적인 데이터 보존 및 관리를 위해 중요한 기능이며, 특히 대량의 데이터가 지속적으로 생성되는 환경에서 더욱 그렇습니다. TTL을 사용하면 오래된 데이터를 자동으로 만료 및 삭제할 수 있으므로, 수동 개입 없이도 스토리지를 최적으로 활용하고 성능을 유지할 수 있습니다. 이 기능은 데이터베이스를 가볍게 유지하고 스토리지 비용을 절감하며, 가장 관련성이 높고 최신인 데이터에 집중함으로써 쿼리가 빠르고 효율적으로 유지되도록 하는 데 필수적입니다. 또한 데이터 수명 주기를 체계적으로 관리해 데이터 보존 정책에 대한 컴플라이언스를 지원하므로, 관측성 솔루션 전반의 지속 가능성과 확장성을 높이는 데 도움이 됩니다.
기본적으로 ClickStack은 데이터를 3일 동안 보존합니다. 이를 변경하려면 "TTL 수정"를 참조하십시오.
ClickHouse에서는 TTL이 테이블 수준에서 제어됩니다. 예를 들어 logs의 기본 스키마는 아래와 같으며, collector가 테이블을 생성할 때 ${TABLES_TTL}은 구성된 보존 기간(변경하지 않은 경우 3일)으로 대체됩니다.
CREATE TABLE IF NOT EXISTS ${DATABASE}.otel_logs
(
`Timestamp` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
`TraceId` String CODEC(ZSTD(1)),
`SpanId` String CODEC(ZSTD(1)),
`TraceFlags` UInt8,
`SeverityText` LowCardinality(String) CODEC(ZSTD(1)),
`SeverityNumber` UInt8,
`ServiceName` LowCardinality(String) CODEC(ZSTD(1)),
`Body` String CODEC(ZSTD(1)),
`ResourceSchemaUrl` LowCardinality(String) CODEC(ZSTD(1)),
`ResourceAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
`ScopeSchemaUrl` LowCardinality(String) CODEC(ZSTD(1)),
`ScopeName` String CODEC(ZSTD(1)),
`ScopeVersion` LowCardinality(String) CODEC(ZSTD(1)),
`ScopeAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
`LogAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
`EventName` String CODEC(ZSTD(1)),
`__hdx_materialized_k8s.cluster.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.cluster.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.container.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.container.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.deployment.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.deployment.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.namespace.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.namespace.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.node.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.node.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.pod.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.pod.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.pod.uid` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.pod.uid'] CODEC(ZSTD(1)),
`__hdx_materialized_deployment.environment.name` LowCardinality(String) MATERIALIZED ResourceAttributes['deployment.environment.name'] CODEC(ZSTD(1)),
`ResourceAttributeItems` Array(String) ALIAS arrayMap((arr) -> concat(arr.1, '=', arr.2), ResourceAttributes::Array(Tuple(String, String))),
`ScopeAttributeItems` Array(String) ALIAS arrayMap((arr) -> concat(arr.1, '=', arr.2), ScopeAttributes::Array(Tuple(String, String))),
`LogAttributeItems` Array(String) ALIAS arrayMap((arr) -> concat(arr.1, '=', arr.2), LogAttributes::Array(Tuple(String, String))),
INDEX idx_trace_id TraceId TYPE text(tokenizer = 'array'),
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE text(tokenizer = 'array'),
INDEX idx_res_attr_items ResourceAttributeItems TYPE text(tokenizer = 'array'),
INDEX idx_scope_attr_key mapKeys(ScopeAttributes) TYPE text(tokenizer = 'array'),
INDEX idx_scope_attr_items ScopeAttributeItems TYPE text(tokenizer = 'array'),
INDEX idx_log_attr_key mapKeys(LogAttributes) TYPE text(tokenizer = 'array'),
INDEX idx_log_attr_items LogAttributeItems TYPE text(tokenizer = 'array'),
INDEX idx_lower_body lower(Body) TYPE text(tokenizer = 'splitByNonAlpha')
)
ENGINE = MergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp)
TTL toDateTime(Timestamp) + ${TABLES_TTL}
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, enable_block_number_column = 1, enable_block_offset_column = 1;ClickHouse의 파티셔닝은 컬럼 또는 SQL 표현식에 따라 디스크에서 데이터를 논리적으로 분리할 수 있게 합니다. 데이터를 논리적으로 분리하면 각 파티션을 독립적으로 처리할 수 있으며, 예를 들어 TTL 정책에 따라 만료되면 삭제할 수 있습니다.
위 예시에서 볼 수 있듯이 파티셔닝은 테이블을 처음 정의할 때 PARTITION BY 절을 통해 지정합니다. 이 절에는 하나 이상의 컬럼에 대한 SQL 표현식을 포함할 수 있으며, 그 결과에 따라 각 행이 어느 파티션으로 전송될지가 결정됩니다. 이로 인해 데이터는 디스크에서 각 파티션과 논리적으로 연결되며(공통 폴더 이름 접두사를 통해), 이후 각 파티션만 따로 쿼리할 수 있습니다. 위 예시에서는 기본 otel_logs 스키마가 toDate(Timestamp) 표현식을 사용해 일 단위로 파티셔닝합니다. 행이 ClickHouse에 삽입되면 이 표현식이 각 행에 대해 평가되고, 해당 파티션이 이미 있으면 그 파티션으로 전달됩니다(해당 날짜의 첫 번째 행이면 파티션이 생성됩니다). 파티셔닝과 그 밖의 활용 사례에 대한 자세한 내용은 "Table Partitions"를 참조하십시오.

테이블 스키마에는 TTL toDateTime(Timestamp) + ${TABLES_TTL}와 설정 ttl_only_drop_parts = 1도 포함됩니다. 앞의 절은 데이터가 구성된 TTL(기본적으로 3일)보다 오래되면 삭제되도록 보장합니다. 설정 ttl_only_drop_parts = 1은 모든 데이터가 만료된 데이터 파트만 만료 대상으로 처리하도록 합니다(행을 부분적으로 삭제하려고 시도하는 대신). 또한 파티셔닝을 통해 서로 다른 날짜의 데이터가 절대 머지되지 않으므로 데이터를 효율적으로 삭제할 수 있습니다.
기본적으로 TTL이 만료된 데이터는 ClickHouse가 데이터 파트를 머지할 때 제거됩니다. ClickHouse가 데이터가 만료되었음을 감지하면 예정되지 않은 머지를 수행합니다.
TTL 수정
TTL은 다음 두 가지 방법으로 수정할 수 있습니다:
- 테이블 스키마를 수정합니다(권장). 이 방법을 사용하려면 clickhouse-client 또는 Cloud SQL Console을 사용해 ClickHouse 인스턴스에 연결해야 합니다. 예를 들어, 다음 DDL을 사용하면
otel_logs테이블의 TTL을 수정할 수 있습니다:
ALTER TABLE default.otel_logs
MODIFY TTL TimestampTime + toIntervalDay(7);- OTel collector를 수정합니다. ClickStack OpenTelemetry collector는 ClickHouse에 테이블이 없으면 생성합니다. 이는 ClickHouse exporter를 통해 수행되며, 이 exporter에는 기본 TTL 표현식을 제어하는 데 사용하는
ttl매개변수가 있습니다. 예:
exporters:
clickhouse:
endpoint: tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1
ttl: 72h컬럼 수준 TTL
위의 예시에서는 데이터를 테이블 수준에서 만료합니다. 데이터는 컬럼 수준에서도 만료할 수 있습니다. 데이터가 오래될수록 조사에 활용했을 때의 가치가 보관에 따른 리소스 오버헤드를 정당화하지 못하는 컬럼을 삭제하는 데 이를 사용할 수 있습니다. 예를 들어, 삽입 시점에 아직 추출되지 않은 새로운 동적 메타데이터(예: 새로운 Kubernetes 레이블)가 추가될 가능성에 대비해 Body 컬럼을 유지할 것을 권장합니다. 예를 들어 1개월 정도 지난 후에는 이러한 추가 메타데이터가 유용하지 않다는 점이 분명해질 수 있으므로 Body 컬럼을 계속 유지할 가치도 낮아집니다.
아래에서는 Body 컬럼을 30일 후 삭제하는 방법을 보여드립니다.
CREATE TABLE otel_logs_v2
(
`Body` String TTL Timestamp + INTERVAL 30 DAY,
`Timestamp` DateTime,
...
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)