Pular para o conteúdo principal

TTL no ClickStack

Time-to-Live (TTL) é um recurso essencial no ClickStack para retenção e gerenciamento eficientes de dados, especialmente considerando que grandes volumes de dados são gerados continuamente. O TTL permite a expiração e exclusão automáticas de dados mais antigos, garantindo o uso ideal do armazenamento e a manutenção do desempenho sem intervenção manual. Essa capacidade é essencial para manter o banco de dados enxuto, reduzir os custos de armazenamento e garantir que as consultas continuem rápidas e eficientes ao se concentrarem nos dados mais relevantes e recentes. Além disso, ela ajuda na conformidade com políticas de retenção de dados ao gerenciar sistematicamente os ciclos de vida dos dados, aumentando assim a sustentabilidade e a escalabilidade gerais da solução de observabilidade. Por padrão, o ClickStack retém os dados por 3 dias. Para modificar isso, consulte “Como modificar o TTL”. O TTL é controlado no nível da tabela no ClickHouse. Por exemplo, o schema padrão para logs é mostrado abaixo; ${TABLES_TTL} é substituído pela retenção configurada (3 dias, a menos que seja alterada) quando o collector cria a tabela:
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;
O particionamento no ClickHouse permite que os dados sejam separados logicamente no disco de acordo com uma coluna ou expressão SQL. Ao separar os dados logicamente, cada partição pode ser operada de forma independente, por exemplo, removida quando expirar de acordo com uma política de TTL. Como mostrado no exemplo acima, o particionamento é especificado em uma tabela quando ela é definida inicialmente por meio da cláusula PARTITION BY. Essa cláusula pode conter uma expressão SQL em qualquer coluna, cujo resultado definirá para qual partição uma linha será enviada. Isso faz com que os dados sejam associados logicamente (por meio de um prefixo comum no nome da pasta) a cada partição no disco, que então pode ser consultada isoladamente. No exemplo acima, o schema padrão otel_logs particiona por dia usando a expressão toDate(Timestamp). À medida que as linhas são inseridas no ClickHouse, essa expressão será avaliada em relação a cada linha e encaminhada para a partição resultante, se ela existir (se a linha for a primeira de um dia, a partição será criada). Para mais detalhes sobre particionamento e suas outras aplicações, consulte “Partições de tabela”. O schema da tabela também inclui TTL toDateTime(Timestamp) + ${TABLES_TTL} e a configuração ttl_only_drop_parts = 1. A primeira cláusula garante que os dados serão removidos quando tiverem mais do que o TTL configurado (3 dias por padrão). A configuração ttl_only_drop_parts = 1 faz com que apenas partes de dados em que todos os dados expiraram sejam removidas (em vez de tentar excluir linhas parcialmente). Como o particionamento garante que dados de dias diferentes nunca sejam “mesclados”, os dados podem, assim, ser removidos com eficiência.
ttl_only_drop_partsRecomendamos sempre usar a configuração ttl_only_drop_parts=1. Quando essa configuração está habilitada, o ClickHouse remove uma parte inteira quando todas as linhas nela expiraram. Remover partes inteiras, em vez de limpar parcialmente linhas expiradas por TTL (o que é feito por meio de mutações que consomem muitos recursos quando ttl_only_drop_parts=0), permite usar tempos menores de merge_with_ttl_timeout e reduzir o impacto no desempenho do sistema. Se os dados forem particionados pela mesma unidade usada para a expiração por TTL, por exemplo, dia, as partes naturalmente conterão apenas dados do intervalo definido. Isso garantirá que ttl_only_drop_parts=1 possa ser aplicado com eficiência.
Por padrão, dados com TTL expirado são removidos quando o ClickHouse mescla partes de dados. Quando o ClickHouse detecta que os dados expiraram, ele executa uma mesclagem fora do cronograma.
Agendamento de TTLTTLs não são aplicados imediatamente, mas sim de acordo com um agendamento, como observado acima. A configuração de tabela MergeTree merge_with_ttl_timeout define o atraso mínimo, em segundos, antes de repetir uma mesclagem com TTL de exclusão. O valor padrão é 14400 segundos (4 horas). Mas esse é apenas o atraso mínimo; a mesclagem de TTL pode demorar mais para ser acionada. Se o valor for muito baixo, muitas mesclagens fora do cronograma serão executadas, o que pode consumir muitos recursos. A expiração de TTL pode ser forçada com o comando ALTER TABLE my_table MATERIALIZE TTL.

Como modificar o TTL

Para modificar o TTL, você pode:
  1. Modificar os schemas da tabela (recomendado). Para isso, é necessário se conectar à instância do ClickHouse, por exemplo, usando o clickhouse-client ou o Cloud SQL Console. Por exemplo, podemos modificar o TTL da tabela otel_logs usando o seguinte DDL:
ALTER TABLE default.otel_logs
MODIFY TTL TimestampTime + toIntervalDay(7);
  1. Modifique o OTel collector. O ClickStack OpenTelemetry collector cria tabelas no ClickHouse caso elas ainda não existam. Isso é feito por meio do ClickHouse exporter, que expõe um parâmetro ttl usado para controlar a expressão TTL padrão, por exemplo.
exporters:
 clickhouse:
   endpoint: tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1
   ttl: 72h

TTL em nível de coluna

Os exemplos acima fazem os dados expirarem no nível da tabela. Você também pode fazer os dados expirarem no nível da coluna. À medida que os dados envelhecem, isso pode ser usado para remover colunas cujo valor nas investigações não justifica o custo adicional de recursos para mantê-las. Por exemplo, recomendamos manter a coluna Body caso novos metadados dinâmicos sejam adicionados e ainda não tenham sido extraídos no momento da inserção, por exemplo, um novo label do Kubernetes. Após um período, por exemplo, de 1 mês, pode ficar evidente que esses metadados adicionais não são úteis, reduzindo assim o valor de manter a coluna Body. Abaixo, mostramos como a coluna Body pode ser removida após 30 dias.
CREATE TABLE otel_logs_v2
(
        `Body` String TTL Timestamp + INTERVAL 30 DAY,
        `Timestamp` DateTime,
 ...
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)
Para especificar um TTL no nível da coluna, os usuários precisam definir seu próprio schema. Isso não pode ser especificado no OTel collector.
Última modificação em 25 de junho de 2026