Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Gerenciamento de TTL

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".

Partições

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.

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.

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)
Navigation