Composants pertinents pour ClickHouse
- L’OpenTelemetry Collector est un proxy qui reçoit, traite et exporte les données de télémétrie. Une solution reposant sur ClickHouse utilise ce composant à la fois pour collecter les logs et traiter les événements avant leur mise en lot et leur insertion.
- Les SDKs de langage qui implémentent la spécification, les API et l’export des données de télémétrie. Ces SDKs garantissent concrètement que les traces sont correctement enregistrées dans le code d’une application, en générant les spans qui les composent et en veillant à ce que le contexte soit propagé entre les services via les métadonnées, ce qui permet de constituer des traces distribuées et de corréler les spans. Ces SDKs sont complétés par un écosystème qui instrumente automatiquement les bibliothèques et frameworks courants, ce qui signifie que l’utilisateur n’a pas besoin de modifier son code et bénéficie d’une instrumentation prête à l’emploi.
Distributions
- Réduire la taille du collecteur, et donc le temps de déploiement du collecteur
- Améliorer la sécurité du collecteur en réduisant la surface d’attaque disponible
Ingestion de données avec OTel
Rôles de déploiement du collecteur
- Agent - Les instances d’agent collectent les données en périphérie, par exemple sur des serveurs ou des nœuds Kubernetes, ou reçoivent directement des événements d’applications instrumentées avec un SDK OpenTelemetry. Dans ce dernier cas, l’instance d’agent s’exécute avec l’application ou sur le même hôte que celle-ci (par exemple en sidecar ou sous forme de DaemonSet). Les agents peuvent envoyer leurs données directement à ClickHouse ou à une instance de passerelle. Dans le premier cas, on parle du modèle de déploiement Agent.
- Gateway - Les instances de passerelle fournissent un service autonome (par exemple, un déploiement dans Kubernetes), généralement par cluster, par centre de données ou par région. Elles reçoivent les événements des applications (ou d’autres collecteurs agissant comme agents) via un point de terminaison OTLP unique. En règle générale, plusieurs instances de passerelle sont déployées, avec un équilibreur de charge prêt à l’emploi pour répartir la charge entre elles. Lorsque tous les agents et toutes les applications envoient leurs signaux vers ce point de terminaison unique, on parle souvent du modèle de déploiement Gateway.
Collecte des logs
- Scraping via le receiver filelog - Ce receiver lit les fichiers sur disque en continu, génère des messages de log, puis les envoie à ClickHouse. Il prend en charge des tâches complexes telles que la détection des messages multilignes, la gestion de la rotation des logs, les checkpoints pour une meilleure robustesse au redémarrage, ainsi que l’extraction de structure. Il peut également suivre les logs des conteneurs Docker et Kubernetes, et être déployé via un chart Helm, en extrayant leur structure et en les enrichissant avec les détails du pod.
Astuce :
otelbin.iootelbin.io est utile pour valider et visualiser les configurations.Structuré ou non structuré
Exemple
json_parser, puisque nos logs sont structurés. Modifiez le chemin vers le fichier access-structured.log.
Envisagez ClickHouse pour le parsingL’exemple ci-dessous extrait le timestamp du log. Cela nécessite l’utilisation de l’opérateur
json_parser, qui convertit toute la ligne de log en chaîne JSON et place le résultat dans LogAttributes. Cette opération peut être coûteuse en calcul et peut être effectuée plus efficacement dans ClickHouse - Extraction de la structure avec SQL. Un exemple non structuré équivalent, qui utilise le regex_parser pour obtenir le même résultat, est disponible ici.filelog) ; par exemple, au lieu de otelcol_0.102.1_darwin_arm64.tar.gz, les utilisateurs devront télécharger otelcol-contrib_0.102.1_darwin_arm64.tar.gz. Les releases sont disponibles ici.
Une fois installé, l’OTel Collector peut être exécuté à l’aide des commandes suivantes :
Body, tandis que le JSON a été extrait automatiquement dans le champ Attributes grâce à json_parser. Ce même operator a également été utilisé pour extraire le timestamp vers la colonne Timestamp appropriée. Pour des recommandations sur le traitement des logs avec OTel, voir Processing.
OpérateursLes opérateurs constituent l’unité la plus élémentaire du traitement des logs. Chaque opérateur remplit une fonction unique, comme lire des lignes depuis un fichier ou analyser du JSON à partir d’un champ. Les opérateurs sont ensuite chaînés dans un pipeline afin d’obtenir le résultat souhaité.
TraceID ni SpanID. S’ils étaient présents, par exemple dans les cas où des utilisateurs mettent en œuvre le traçage distribué, ils pourraient être extraits du JSON en utilisant les mêmes techniques que celles présentées ci-dessus.
Pour les utilisateurs qui doivent collecter des fichiers de log locaux ou des fichiers de log Kubernetes, nous recommandons de se familiariser avec les options de configuration disponibles pour le filelog receiver, ainsi qu’avec la façon dont le suivi des offsets et l’analyse multiligne des logs sont gérés.
Collecte des logs Kubernetes
ResourceAttributes. ClickHouse utilise actuellement le type Map(String, String) pour cette colonne. Voir Using Maps et Extracting from maps pour plus de détails sur la gestion et l’optimisation de ce type.
Collecte de traces
Exemple
telemetrygen pour générer des données de trace. Suivez les instructions d’installation ici.
La configuration suivante reçoit des événements de trace via un receiver OTLP avant de les envoyer vers stdout.
config-traces.xml
telemetrygen :
Traitement - filtrage, transformation et enrichissement
-
Processors - Les processors prennent les données collectées par les receivers et les modifient ou les transforment avant de les envoyer aux exporters. Les processors sont appliqués dans l’ordre défini dans la section
processorsde la collector configuration. Ils sont facultatifs, mais l’ensemble minimal est généralement recommandé. Lors de l’utilisation d’un OTel collector avec ClickHouse, nous recommandons de limiter les processors à :- Un memory_limiter sert à éviter les situations de saturation mémoire sur le collector. Consultez Estimating Resources pour les recommandations.
- Tout processor qui effectue un enrichissement basé sur le contexte. Par exemple, le Kubernetes Attributes Processor permet de définir automatiquement les resource attributes des spans, metrics et logs à partir des métadonnées k8s, par ex. en enrichissant les événements avec l’identifiant de leur pod source.
- Le tail sampling ou head sampling, si nécessaire pour les traces.
- Le filtrage de base - suppression des événements inutiles si cela ne peut pas être fait via un opérateur (voir ci-dessous).
- Le batching - indispensable avec ClickHouse pour garantir l’envoi des données par lots. Voir « Exporting to ClickHouse ».
- Opérateurs - Les opérateurs constituent l’unité de traitement la plus élémentaire disponible au niveau du receiver. Ils prennent en charge le parsing de base, ce qui permet de définir des champs tels que Severity et Timestamp. Le parsing JSON et regex est pris en charge ici, ainsi que le filtrage d’événements et les transformations de base. Nous recommandons d’effectuer le filtrage des événements à ce niveau.
Exemple
regex_parser) et filtrer les événements, ainsi que d’un processeur pour traiter les événements par lot et limiter la consommation mémoire.
config-unstructured-logs-with-processor.yaml
Exportation vers ClickHouse
Utilisez OpenTelemetry Collector ContribLe ClickHouse exporter fait partie d’OpenTelemetry Collector Contrib, et non de la distribution principale. Vous pouvez soit utiliser la distribution contrib, soit compiler votre propre collector.
- pipelines - La configuration ci-dessus met en évidence l’utilisation des pipelines, composés d’un ensemble de receivers, de processeurs et d’exporters, avec un pipeline pour les logs et un pour les traces.
- endpoint - La communication avec ClickHouse est configurée via le paramètre
endpoint. La chaîne de connexiontcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1fait en sorte que la communication s’effectue sur TCP. Si vous préférez HTTP pour des raisons de basculement de trafic, modifiez cette chaîne de connexion comme décrit ici. Les détails complets de la connexion, y compris la possibilité de spécifier un nom d’utilisateur et un mot de passe dans cette chaîne de connexion, sont décrits ici.
- ttl - la valeur indiquée ici détermine la durée de conservation des données. Pour plus de détails, voir “Gestion des données”. Cette valeur doit être spécifiée sous forme d’une unité de temps en heures, par exemple 72h. Nous désactivons le TTL dans l’exemple ci-dessous, car nos données datent de 2019 et seront immédiatement supprimées par ClickHouse si elles sont insérées.
- traces_table_name et logs_table_name - détermine le nom des tables de logs et de traces.
- create_schema - détermine si les tables sont créées avec les schémas par défaut au démarrage. La valeur par défaut est true pour démarrer. Vous devriez le définir sur false et définir votre propre schéma.
- database - base de données cible.
- retry_on_failure - paramètres qui déterminent si les batches en échec doivent être retentés.
- batch - un batch processor garantit que les événements sont envoyés par batches. Nous recommandons une valeur d’au moins 10 000 avec un timeout de 5s (des valeurs allant jusqu’à 100 000 peuvent être utilisées si la mémoire le permet). Le premier de ces seuils atteint déclenchera l’envoi d’un batch à l’exportateur. Réduire ces valeurs permet d’obtenir un pipeline à plus faible latence, avec des données disponibles plus tôt pour les requêtes, au prix d’un plus grand nombre de connections et de batches envoyés à ClickHouse. Cela n’est pas recommandé si vous n’utilisez pas les insertions asynchrones, car cela peut provoquer des problèmes de Too many parts dans ClickHouse. À l’inverse, si vous utilisez les insertions asynchrones, la disponibilité de ces données pour les requêtes dépendra également des paramètres d’insertion asynchrone, même si les données seront tout de même transmises plus tôt par le connecteur. Consultez regroupement par lots pour plus de détails.
- sending_queue - contrôle la taille de la file d’envoi. Chaque élément de la file contient un batch. Si cette file est saturée, par exemple parce que ClickHouse est inaccessible alors que les événements continuent d’arriver, les batches seront abandonnés.
telemetrygen :
Schéma prêt à l’emploi
create_schema. En outre, les noms des tables de logs et de traces peuvent être modifiés par rapport à leurs valeurs par défaut, otel_logs et otel_traces, via les paramètres mentionnés ci-dessus.
Dans les schémas ci-dessous, nous supposons que TTL est configuré à 72 h.
otelcol-contrib v0.102.1) :
- Par défaut, la table est partitionnée par date via
PARTITION BY toDate(Timestamp). Cela permet de supprimer efficacement les données expirées. - Le TTL est défini via
TTL toDateTime(Timestamp) + toIntervalDay(3)et correspond à la valeur définie dans la configuration du collector.ttl_only_drop_parts=1signifie que seules des parts entières sont supprimées lorsque toutes les lignes qu’elles contiennent ont expiré. C’est plus efficace que de supprimer des lignes à l’intérieur des parts, ce qui implique undeletecoûteux. Nous recommandons de toujours définir ce paramètre. Consultez Gestion des données avec TTL pour plus de détails. - La table utilise le moteur classique
MergeTree. C’est la recommandation pour les logs et les traces, et il ne devrait pas être nécessaire de le modifier. - La table est ordonnée par
ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId). Cela signifie que les requêtes seront optimisées pour les filtres surServiceName,SeverityText,TimestampetTraceId: les colonnes placées plus tôt dans la liste seront filtrées plus rapidement que les suivantes ; par exemple, un filtrage surServiceNamesera nettement plus rapide qu’un filtrage surTraceId. Vous devez modifier cet ordre en fonction des schémas d’accès attendus ; voir Choisir une clé primaire. - Le schéma ci-dessus applique
ZSTD(1)aux colonnes. Cela offre la meilleure compression pour les logs. Vous pouvez augmenter le niveau de compression ZSTD (au-dessus de la valeur par défaut de 1) pour obtenir une meilleure compression, bien que cela soit rarement avantageux. Augmenter cette valeur entraînera une surcharge CPU plus importante au moment de l’insertion (pendant la compression), même si la décompression (et donc les requêtes) devrait rester comparable. Consultez ce lien pour plus de détails. Un encodage delta supplémentaire est appliqué àTimestampafin de réduire sa taille sur disque. - Notez que
ResourceAttributes,LogAttributesetScopeAttributessont des maps. Il est important de bien comprendre les différences entre ces éléments. Consultez “Utiliser les maps” pour savoir comment accéder à ces maps et optimiser l’accès aux clés qu’elles contiennent. - La plupart des autres types ici, par exemple
ServiceNameenLowCardinality, sont optimisés. Notez queBody, qui est du JSON dans nos logs d’exemple, est stocké sous forme deString. - Des bloom filters sont appliqués aux clés et valeurs des maps, ainsi qu’à la colonne
Body. Ils visent à améliorer le temps d’exécution des requêtes qui accèdent à ces colonnes, mais ne sont généralement pas nécessaires. Consultez Indices secondaires / data skipping indices.
Optimisation des insertions
Regroupement par lots
- (1) Si le nœud qui reçoit les données rencontre un problème, la requête d’insertion expire (ou renvoie une erreur plus spécifique) et ne reçoit pas d’accusé de réception.
- (2) Si les données ont bien été écrites par le nœud, mais que l’accusé de réception ne peut pas être renvoyé à l’émetteur de la requête en raison d’interruptions réseau, l’émetteur recevra soit un délai d’expiration, soit une erreur réseau.
timeout du batch processor ne soit atteint, ce qui garantit une faible latence de bout en bout du pipeline ainsi qu’une taille de lot cohérente.
Utiliser les insertions asynchrones
timeout du batch processor. Cela peut poser problème, et c’est précisément dans ce type de situation que les insertions asynchrones deviennent nécessaires. Ce cas se présente généralement lorsque des collectors dans le rôle d’agent sont configurés pour envoyer directement vers ClickHouse. Les passerelles, en jouant un rôle d’agrégation, peuvent atténuer ce problème ; voir Scaling with Gateways.
S’il n’est pas possible de garantir de gros lots, vous pouvez déléguer le regroupement par lots à ClickHouse en utilisant les insertions asynchrones. Avec les insertions asynchrones, les données sont d’abord insérées dans un buffer, puis écrites ultérieurement dans le stockage de la base de données, donc de manière asynchrone.
Lorsque les insertions asynchrones sont activées, quand ClickHouse ① reçoit une insert query, les données de la requête sont ② immédiatement écrites dans un buffer en mémoire. Lors du ③ prochain flush du buffer, les données du buffer sont triées et écrites sous forme de part dans le stockage de la base de données. Notez que les données ne peuvent pas être interrogées avant d’avoir été flushées vers le stockage de la base de données ; le flush du buffer est configurable.
Pour activer les insertions asynchrones pour le collector, ajoutez async_insert=1 à la chaîne de connexion. Nous recommandons d’utiliser wait_for_async_insert=1 (valeur par défaut) afin d’obtenir des garanties de livraison ; voir ici pour plus de détails.
Les données d’une async insert sont insérées une fois le buffer ClickHouse flushé. Cela se produit soit lorsque async_insert_max_data_size est dépassé, soit après async_insert_busy_timeout_ms millisecondes à partir de la première INSERT query. Si async_insert_stale_timeout_ms est défini sur une valeur non nulle, les données sont insérées après async_insert_stale_timeout_ms milliseconds depuis la dernière query. Vous pouvez ajuster ces settings pour contrôler la latence de bout en bout de votre pipeline. D’autres settings permettant d’ajuster le flush du buffer sont documentés ici. En règle générale, les valeurs par défaut conviennent.
Envisagez les insertions asynchrones adaptativesDans les cas où un petit nombre d’agents est utilisé, avec un débit faible mais des exigences strictes en matière de latence de bout en bout, les insertions asynchrones adaptatives peuvent être utiles. En règle générale, elles ne s’appliquent pas aux cas d’usage d’observability à haut débit, comme ceux observés avec ClickHouse.
async_insert_deduplicate.
Tous les détails sur la configuration de cette fonctionnalité sont disponibles ici, avec une analyse approfondie ici.
Architectures de déploiement
Agents uniquement
- Mise à l’échelle des connexions - Chaque agent établit une connexion à ClickHouse. Bien que ClickHouse puisse maintenir des centaines, voire des milliers, de connexions d’insertion concurrentes, cela finit par devenir un facteur limitant et par rendre les insertions moins efficaces : ClickHouse consacre alors davantage de ressources au maintien de ces connexions. L’utilisation de passerelles réduit le nombre de connexions et améliore l’efficacité des insertions.
- Traitement à la périphérie - Dans cette architecture, toute transformation ou tout traitement des événements doit être effectué à la périphérie ou dans ClickHouse. En plus d’être contraignant, cela peut impliquer soit des vues matérialisées ClickHouse complexes, soit le déplacement d’une part importante du calcul vers la périphérie, où les ressources sont limitées et les services critiques peuvent être affectés.
- Petits lots et latences - Les collectors agents peuvent, individuellement, collecter très peu d’événements. Cela signifie généralement qu’ils doivent être configurés pour envoyer leurs données à intervalle défini afin de respecter les SLA de livraison. Le collector peut alors envoyer de petits lots à ClickHouse. Bien que ce soit un inconvénient, il peut être atténué avec les insertions asynchrones - voir Optimiser les insertions.