TTL dans ClickStack
Le time-to-live (TTL) est une fonctionnalité essentielle de ClickStack pour assurer une rétention et une gestion efficaces des données, en particulier compte tenu des volumes considérables de données générées en continu. Le TTL permet l’expiration et la suppression automatiques des données les plus anciennes, garantissant une utilisation optimale du stockage et le maintien des performances sans intervention manuelle. Cette capacité est essentielle pour alléger la base de données, réduire les coûts de stockage et garantir des requêtes rapides et efficaces en se concentrant sur les données les plus pertinentes et les plus récentes. De plus, elle aide à respecter les politiques de rétention des données en gérant systématiquement leur cycle de vie, ce qui renforce la durabilité et la scalabilité globales de la solution d’observability.
Par défaut, ClickStack conserve les données pendant 3 jours. Pour modifier ce paramètre, voir "Modification du TTL".
Le TTL est contrôlé au niveau de la table dans ClickHouse. Par exemple, le schéma par défaut des logs est présenté ci-dessous ; ${TABLES_TTL} est remplacé par la durée de rétention configurée (3 jours sauf modification) lorsque le collector crée la table :
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;Le partitionnement dans ClickHouse permet de séparer logiquement les données sur disque selon une colonne ou une expression SQL. En séparant logiquement les données, chaque partition peut être traitée indépendamment, par exemple supprimée lorsqu’elle expire conformément à une politique TTL.
Comme le montre l’exemple ci-dessus, le partitionnement est spécifié sur une table lors de sa définition initiale via la clause PARTITION BY. Cette clause peut contenir une expression SQL sur une ou plusieurs colonnes, dont le résultat détermine vers quelle partition une ligne est envoyée. Les données sont ainsi logiquement associées (via un préfixe commun de nom de dossier) à chaque partition sur le disque, qui peut ensuite être interrogée isolément. Dans l’exemple ci-dessus, le schéma otel_logs par défaut partitionne les données par jour à l’aide de l’expression toDate(Timestamp). À mesure que des lignes sont insérées dans ClickHouse, cette expression est évaluée pour chaque ligne et celle-ci est dirigée vers la partition correspondante si elle existe (si la ligne est la première d’une journée, la partition sera créée). Pour plus de détails sur le partitionnement et ses autres applications, voir "Partitions de table".

Le schéma de la table inclut également TTL toDateTime(Timestamp) + ${TABLES_TTL} et le paramètre ttl_only_drop_parts = 1. La première clause garantit que les données seront supprimées une fois qu’elles dépassent le TTL configuré (3 jours par défaut). Le paramètre ttl_only_drop_parts = 1 fait en sorte que seules les data parts dont toutes les données ont expiré soient supprimées (au lieu de tenter une suppression partielle des lignes). Comme le partitionnement garantit que les données de jours distincts ne sont jamais « fusionnées », elles peuvent ainsi être supprimées efficacement.
Par défaut, les données dont le TTL a expiré sont supprimées lorsque ClickHouse fusionne les data parts. Lorsque ClickHouse détecte que des données ont expiré, il effectue une fusion hors planification.
Modification du TTL
Pour modifier le TTL, vous avez deux possibilités :
- Modifier les schémas des tables (recommandé). Pour cela, vous devez vous connecter à l’instance ClickHouse, par exemple avec clickhouse-client ou la Cloud SQL Console. Par exemple, vous pouvez modifier le TTL de la table
otel_logsà l’aide de l’instruction DDL suivante :
ALTER TABLE default.otel_logs
MODIFY TTL TimestampTime + toIntervalDay(7);- Modifiez l’OTel collector. Le ClickStack OpenTelemetry collector crée des tables dans ClickHouse si elles n’existent pas. Cela se fait via le ClickHouse exporter, qui expose lui-même un paramètre
ttlpermettant de contrôler l’expression TTL par défaut, par exemple.
exporters:
clickhouse:
endpoint: tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1
ttl: 72hTTL au niveau de la colonne
Les exemples ci-dessus appliquent l’expiration des données au niveau de la table. Vous pouvez également définir une expiration au niveau de la colonne. À mesure que les données vieillissent, cela permet de supprimer les colonnes dont l’utilité dans les analyses ne justifie plus les ressources nécessaires à leur conservation. Par exemple, nous recommandons de conserver la colonne Body au cas où de nouvelles métadonnées dynamiques seraient ajoutées sans avoir été extraites au moment de l’insertion, par exemple un nouveau label Kubernetes. Après un certain temps, par exemple 1 mois, il peut devenir évident que ces métadonnées supplémentaires ne sont pas utiles, ce qui réduit l’intérêt de conserver la colonne Body.
Ci-dessous, nous montrons comment supprimer la colonne Body après 30 jours.
CREATE TABLE otel_logs_v2
(
`Body` String TTL Timestamp + INTERVAL 30 DAY,
`Timestamp` DateTime,
...
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)