ClickStack 中的生存时间 (TTL)
生存时间 (TTL) 是 ClickStack 中一项至关重要的功能,可用于高效的数据保留和管理,尤其是在系统持续生成海量数据的情况下。TTL 允许较旧的数据自动过期并删除,从而确保存储得到最佳利用,并在无需人工干预的情况下维持性能。这项能力对于保持数据库精简、降低存储成本,以及通过聚焦最相关、最新的数据来确保查询持续快速高效至关重要。此外,它还能通过系统化管理数据生命周期,帮助满足数据保留策略方面的合规要求,从而提升整个可观测性解决方案的可持续性和可扩展性。
默认情况下,ClickStack 会保留 3 天的数据。要修改此设置,请参阅“修改生存时间 (TTL)”。
在 ClickHouse 中,生存时间 (TTL) 在表级别控制。例如,下面显示的是日志的默认 schema;当 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 schema 使用表达式 toDate(Timestamp) 按天分区。当行被插入到 ClickHouse 时,系统会针对每一行计算该表达式,并将其路由到对应的分区 (如果该分区存在;如果该行是某一天的第一行,则会创建该分区)。有关分区及其其他用途的更多详细信息,请参阅“表分区”。

表的 schema 还包含 TTL toDateTime(Timestamp) + ${TABLES_TTL} 以及设置 ttl_only_drop_parts = 1。前一个子句可确保数据在超过已配置的生存时间 (TTL) 后被删除 (默认 3 天)。设置 ttl_only_drop_parts = 1 会强制仅在整个数据 parts 中的数据都已过期时才删除该数据 parts (而不是尝试部分删除行)。由于分区可确保不同日期的数据永远不会被“merged”,因此可以高效地删除数据。
默认情况下,生存时间 (TTL) 已过期的数据会在 ClickHouse 合并数据 parts 时被移除。当 ClickHouse 检测到数据已过期时,它会执行一次计划外合并。
修改生存时间 (TTL)
要修改生存时间 (TTL),可以采用以下任一方式:
- 修改表的 schema (推荐) 。这需要连接到 ClickHouse 实例,例如使用 ClickHouse 客户端 或 Cloud SQL 控制台。例如,可以使用以下 DDL 修改
otel_logs表的生存时间 (TTL):
ALTER TABLE default.otel_logs
MODIFY TTL TimestampTime + toIntervalDay(7);- 修改 OTel collector。ClickStack OpenTelemetry collector 会在 ClickHouse 中自动创建不存在的表。这是通过 ClickHouse exporter 实现的;该组件提供了一个
ttl参数,用于控制默认的生存时间 (TTL) 表达式,例如
exporters:
clickhouse:
endpoint: tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1
ttl: 72h列级生存时间 (TTL)
上述示例是在表级别让数据过期。你也可以在列级别让数据过期。随着数据逐渐变旧,可以删除那些在调查中的作用不足以抵消其保留成本的列。例如,我们建议保留 Body 列,以防后续新增了尚未在写入时提取的动态元数据,例如新的 Kubernetes 标签。经过一段时间 (例如 1 个月) 后,可能就会明显看出,这些额外元数据并没有实际用处——因此继续保留 Body 列的价值也就有限了。
下面我们展示如何在 30 天后删除 Body 列。
CREATE TABLE otel_logs_v2
(
`Body` String TTL Timestamp + INTERVAL 30 DAY,
`Timestamp` DateTime,
...
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)