Introduction
Concepts ClickHouse
| Type de données | Table |
|---|---|
| Logs | otel_logs |
| Traces | otel_traces |
| Métriques (jauges) | otel_metrics_gauge |
| Métriques (sommes) | otel_metrics_sum |
| Métriques (histogramme) | otel_metrics_histogram |
| Métriques (histogrammes exponentiels) | otel_metrics_exponentialhistogram |
| Métriques (résumé) | otel_metrics_summary |
| Sessions | hyperdx_sessions |
default est utilisée ; cela peut être modifié dans l’OpenTelemetry collector.
Vous devez au minimum comprendre les principes fondamentaux suivants de ClickHouse :
| Concept | Description |
|---|---|
| Tables | Comment les sources de données dans ClickStack correspondent aux tables ClickHouse sous-jacentes. Dans ClickHouse, les tables utilisent principalement le moteur MergeTree. |
| Parts | Comment les données sont écrites sous forme de parts immuables, puis fusionnées au fil du temps. |
| Partitions | Les partitions regroupent les parts de données d’une table en unités logiques organisées. Ces unités sont plus faciles à gérer, à interroger et à optimiser. |
| Merges | Processus interne de fusion des parts afin de réduire le nombre de parts à interroger. Indispensable au maintien des performances des requêtes. |
| Granules | La plus petite unité de données que ClickHouse lit et écarte lors de l’exécution d’une requête. |
| Primary (ordering) keys | Comment la clé ORDER BY détermine l’organisation des données sur disque, la compression et l’élagage des requêtes. |
- Créer des tables dans ClickHouse - Introduction simple aux tables.
- Parts
- Partitions
- Merges
- Clés primaires/indexes
- Comment ClickHouse stocke les données : parts et granules - Guide plus avancé expliquant comment les données sont structurées et interrogées dans ClickHouse, avec une présentation détaillée des granules et des clés primaires.
- MergeTree- Guide de référence avancé sur MergeTree, utile pour les commandes et les détails internes.
Optimisation 1. Matérialiser les attributs fréquemment interrogés
LogAttributes, ScopeAttributes et ResourceAttributes, puis à les convertir en colonnes de premier niveau à l’aide de colonnes matérialisées.
À elle seule, cette optimisation suffit souvent à faire évoluer des déploiements ClickStack jusqu’à plusieurs dizaines de téraoctets par jour et doit être appliquée avant d’envisager des techniques d’optimisation plus avancées.
Pourquoi matérialiser les attributs
Map(String, String). Bien que cela offre de la souplesse, interroger des sous-clés d’une map a une incidence importante sur les performances.
Lorsqu’une seule clé est interrogée dans une colonne Map, ClickHouse doit lire toute la colonne map depuis le disque. Si la map contient de nombreuses clés, cela entraîne des E/S inutiles et des requêtes plus lentes que la lecture d’une colonne dédiée.
Matérialiser les attributs fréquemment consultés permet d’éviter cette surcharge en extrayant la valeur au moment de l’insertion et en la stockant dans une colonne à part entière.
Colonnes matérialisées :
- Sont calculées automatiquement lors des insertions
- Ne peuvent pas être définies explicitement dans des instructions INSERT
- Prennent en charge toute expression ClickHouse
- Permettent de convertir String en types numériques ou Date plus efficaces
- Permettent d’utiliser des skip indexes et la clé primaire
- Réduisent les lectures sur disque en évitant l’accès à l’intégralité de la map
ClickStack détecte automatiquement les colonnes matérialisées extraites de maps et les utilise de manière transparente lors de l’exécution des requêtes, même si les utilisateurs continuent d’interroger le chemin d’attribut d’origine.
Exemple
ResourceAttributes :
ResourceAttributes.k8s.pod.name:"checkout-675775c4cc-f2p9c" :
Cela produit un prédicat SQL similaire à :
ResourceAttributes pour chaque ligne correspondante, ce qui peut être très volumineux si cette Map contient de nombreuses clés.
Si cet attribut est fréquemment utilisé dans les requêtes, il doit être matérialisé en tant que colonne de premier niveau.
Pour extraire le nom du pod au moment de l’insertion, ajoutez une colonne matérialisée :
PodName.
Les utilisateurs peuvent désormais interroger efficacement les noms de pod à l’aide de la syntaxe Lucene, par exemple PodName:"checkout-675775c4cc-f2p9c"
Pour les données nouvellement insérées, cela évite complètement l’accès à la structure Map et réduit considérablement les E/S.
Cependant, même si les utilisateurs continuent d’interroger le chemin d’attribut d’origine, par exemple ResourceAttributes.k8s.pod.name:"checkout-675775c4cc-f2p9c", ClickStack réécrira automatiquement la requête en interne pour utiliser la colonne matérialisée PodName, c.-à-d. en utilisant le prédicat :
Par défaut, les colonnes matérialisées sont exclues des
requêtes SELECT *. Cela préserve le principe selon lequel les résultats des requêtes peuvent toujours être réinsérés dans la table.Matérialisation des données historiques
system.mutations, par exemple.
is_done = 1 pour la mutation concernée.
Optimisation 2. Ajout d’index de saut
- Le filtrage de chaînes à forte cardinalité, comme TraceId, les identifiants de session, les clés d’attribut ou les valeurs
- Le filtrage sur des sous-clés de Map, accéléré par des index de texte sur les colonnes
*AttributeItems - Le filtrage sur des plages numériques, comme la durée des spans
text(tokenizer = 'array') au lieu de bloom filters, et ajoute un index text(tokenizer = 'splitByNonAlpha') sur lower(Body) pour la recherche en texte intégral. Voir “Tables and schemas used by ClickStack” pour le DDL complet.
Filtres de Bloom
PodName:"checkout-675775c4cc-f2p9c".
Les filtres de Bloom sont les plus efficaces lorsque la répartition des valeurs fait qu’une valeur donnée apparaît dans un nombre relativement faible de parties. C’est souvent naturellement le cas dans les charges de travail d’observabilité, où des métadonnées comme les noms de pod, les trace IDs ou les identifiants de session sont corrélés au temps et donc regroupés selon la clé de tri de la table.
Comme pour tous les index de saut, les filtres de Bloom doivent être ajoutés de manière sélective et validés sur des modèles de requêtes réels afin de s’assurer qu’ils apportent un bénéfice mesurable - voir “Évaluer l’efficacité des index de saut.”
Index de texte
WHERE. Les index de texte sont des index inversés qui associent des tokens à des offsets exacts dans une part. Comme ils évaluent les offsets plutôt que les granules et ne produisent aucun faux positif, ils peuvent généralement répondre à la condition WHERE sans charger la colonne sous-jacente. Cette optimisation est appelée direct read. Comme le chargement des données est souvent le principal facteur du temps de requête, direct read peut réduire sensiblement la latence des requêtes.
De plus, les index de texte peuvent eux-mêmes être interrogés, ce qui alimente l’autocomplétion et d’autres mécanismes d’introspection dans ClickStack.
Deux tokenizers couvrent la plupart des cas d’usage de ClickStack :
| Tokenizer | Utilisé pour | Colonne typique |
|---|---|---|
array | Indexation des éléments Array(String) comme tokens complets | mapKeys(...), *AttributeItems |
splitByNonAlpha | Recherche en texte intégral au niveau des mots dans des chaînes de texte libre | Body, lower(Body), SpanName |
Tokenizer array pour les colonnes Map et Array
mapKeys et les tableaux d’éléments matérialisés avec le tokenizer array :
splitByNonAlpha pour le corps des logs
Body bénéficie d’un index textuel splitByNonAlpha.
ClickStack définit cet index sur lower(Body) afin que les recherches Lucene insensibles à la casse
puissent l’utiliser :
text(tokenizer = 'splitByNonAlpha') sur
lower(Body), il réécrit les requêtes Lucene à colonne implicite, comme error ou
"connection refused", en hasAllTokens(lower(Body), lower(...)), que
l’index peut satisfaire sans lire l’intégralité de la colonne Body. Pour la plupart des
charges de travail de logs d’observabilité, il s’agit du gain de vitesse de filtrage le plus
important disponible.
Index de texte vs
tokenbf_v1L’ancien type d’index tokenbf_v1 (toujours utilisé dans le schéma de traces par défaut pour
lower(SpanName)) est fonctionnellement similaire, mais est obsolète dans ClickHouse 26.2
et les versions ultérieures. Les nouveaux index de recherche en texte intégral doivent utiliser text(tokenizer = ...).Index de texte dans le schéma de logs par défaut
otel_logs par défaut, synchronisé depuis l’amont, inclut tous les index de texte mentionnés ci-dessus : text(tokenizer = 'array') sur TraceId, sur chaque mapKeys(...) et tableau *AttributeItems, ainsi que text(tokenizer = 'splitByNonAlpha') sur lower(Body) pour la recherche en texte intégral. Pour le DDL canonique, voir “Tables et schémas utilisés par ClickStack” ; le même schéma est reproduit ci-dessous.
Index min-max
SpanAttributes :
Matérialiser un index de saut
Matérialisation des index de sautLa matérialisation d’un index de saut est généralement peu coûteuse et peut être exécutée en toute sécurité, en particulier pour les index minmax. Pour les index filtre de Bloom sur de gros volumes de données, les utilisateurs peuvent préférer matérialiser partition par partition afin de mieux maîtriser l’utilisation des ressources, par exemple.
is_done soit égale à 1 pour la mutation correspondante.
Une fois l’opération terminée, vérifiez que les données de l’index ont bien été créées :
0.01 à 0.05 produit un index plus petit, évalué plus rapidement, au prix d’un élagage moins agressif. Même si moins de granules peuvent être ignorés, la latence globale des requêtes peut s’améliorer grâce à une évaluation plus rapide de l’index.
Le réglage des paramètres du filtre de Bloom est donc une optimisation qui dépend de la charge de travail et doit être validée à l’aide de query patterns réels et de volumes de données proches de ceux de la production.
Pour plus de détails sur les index de saut, consultez le guide “Comprendre les data skipping indexes dans ClickHouse.”
Évaluer l’efficacité des index de saut
EXPLAIN indexes = 1, qui indique combien de parts et de granules sont éliminées à chaque étape du plan de requête. Dans la plupart des cas, on cherche à observer une forte réduction du nombre de granules à l’étape Skip, idéalement après que la clé primaire a déjà réduit l’espace de recherche. Les index de saut sont évalués après le partition élagage et l’élagage de la clé primaire ; leur impact se mesure donc le mieux par rapport aux parts et granules restants.
EXPLAIN confirme si l’élagage a bien lieu, mais ne garantit pas pour autant un gain net de performances. L’évaluation des index de saut a un coût, surtout si l’index est volumineux. Comparez toujours les performances des requêtes avant et après l’ajout et la matérialisation d’un index afin de confirmer un réel gain de performances.
Par exemple, prenons l’index de saut filtre de Bloom par défaut pour TraceId inclus dans le schéma Traces par défaut :
EXPLAIN indexes = 1 pour voir son efficacité sur une requête sélective :
FORMAT Null pour éviter la surcharge liée à la sérialisation des résultats, et désactivez le cache des conditions de requête afin de garantir la reproductibilité des exécutions :
use_query_condition_cache garantit que les résultats ne sont pas influencés par des décisions de filtrage mises en cache, et le paramètre use_skip_indexes = 0 établit une base de référence claire pour la comparaison. Si l’élagage est efficace et que le coût d’évaluation de l’index est faible, la requête avec index devrait être sensiblement plus rapide, comme dans l’exemple ci-dessus.
Quand ajouter des skip indexes
IN que les Bloom filters, et permettent en plus les prédicats basés sur des tokens (hasToken, hasAllTokens, has) utilisés par la recherche en texte intégral et l’optimisation Map direct read. Sur les clusters plus anciens qui ne prennent pas encore en charge les index textuels, les Bloom filters restent un choix solide.
Les Bloom filters sont particulièrement efficaces pour les colonnes de type chaîne à forte cardinalité, où chaque valeur apparaît relativement peu souvent, ce qui signifie que la plupart des parts et des granules ne contiennent pas la valeur recherchée. En règle générale, les Bloom filters sont surtout prometteurs lorsque la colonne contient au moins 10 000 valeurs distinctes, et donnent souvent les meilleurs résultats à partir de 100 000 valeurs distinctes. Ils sont également plus efficaces lorsque les valeurs correspondantes sont regroupées dans un petit nombre de parts séquentielles, ce qui se produit généralement lorsque la colonne est corrélée à la clé de tri. Là encore, cela dépend fortement des cas : rien ne remplace des tests en conditions réelles.
Optimisation 3. Map direct read
LogAttributes['k8s.pod.name'] = 'checkout', ClickHouse doit lire l’intégralité de la colonne Map LogAttributes depuis
le disque et décompacter chaque ligne pour évaluer le prédicat. Matérialiser les attributs fréquemment interrogés
résout ce problème pour les clés connues à l’avance, mais cette approche ne passe pas à l’échelle pour
des attributs arbitraires sur lesquels les utilisateurs filtrent à la volée.
Même si un schéma possède des index sur mapKeys et mapValues, ces index peuvent indiquer si une ligne contient une clé donnée, et si elle contient une valeur donnée, mais pas si la clé et la valeur appartiennent à la même entrée. Autrement dit, mapKeys répond à mapContainsKey(ResourceAttributes, 'foo') et mapValues répond à mapContainsValue(ResourceAttributes, 'bar'), mais aucun des deux ne répond à ResourceAttributes['foo'] = 'bar'.
En concaténant les clés et les valeurs dans une seule colonne Array(String), l’optimisation
Map direct read permet de répondre à ResourceAttributes['foo'] = 'bar' sans
charger la map sous-jacente. Les maps sont souvent volumineuses et grossissent
avec l’augmentation du volume. Combinés à une réécriture de requête au niveau de l’application,
les filtres d’égalité sur n’importe quelle sous-clé de Map deviennent un simple appel has(...)
s’appuyant sur cet index, sans désérialisation de Map au moment de la requête. De plus,
le seul coût de stockage est celui du text index, car la colonne sous-jacente est
une colonne ALIAS et n’est pas stockée.
Cette optimisation est automatique. ClickStack fournit les colonnes et
index nécessaires dans les tables de logs et de traces par défaut, et réécrit les
filtres d’accès aux sous-clés de Map à l’exécution lorsque le ClickHouse server connecté prend en charge la
primitive sous-jacente. Si votre schéma ne contient pas ces colonnes, ou si vous avez
des colonnes Map supplémentaires que vous souhaitez accélérer au-delà de celles fournies par défaut, poursuivez votre lecture pour
les activer.
Schéma
Array(String) ALIAS qui concatène chaque clé et sa valeur avec = :
text(tokenizer = 'array') sur
la colonne ALIAS stocke un token par paire key=value, que ClickHouse utilise
pour ignorer des granules sans toucher à la Map source :
| Table | colonnes ALIAS | index textuels |
|---|---|---|
otel_logs | ResourceAttributeItems, ScopeAttributeItems, LogAttributeItems | idx_res_attr_items, idx_scope_attr_items, idx_log_attr_items |
otel_traces | ResourceAttributeItems, SpanAttributeItems | idx_res_attr_items, idx_span_attr_items |
Réécriture de requête
LogAttributeItems, élimine des
lignes entières qui ne contiennent pas le jeton key=value, et ne désérialise jamais la
Map LogAttributes d’origine pour les lignes non correspondantes. Pour les workloads
d’observabilité à forte cardinalité, cela permet généralement de réduire d’un ordre de grandeur
les E/S par rapport à l’accès à la Map par indice.
La réécriture s’effectue automatiquement — les requêtes enregistrées, les tableaux de bord et les alertes qui
font référence à LogAttributes['key'] bénéficient de cette accélération sans aucune modification.
Exigences de version de ClickHouse
SELECT version(), mise en cache par connexion) et n’émet
la forme réécrite que lorsque le serveur atteint ou dépasse le seuil. Les
serveurs plus anciens reviennent automatiquement à la forme d’indice Map d’origine.
| Branche ClickHouse | Version minimale |
|---|---|
| 26.2 | 26.2.19.43 |
| 26.3 | 26.3.12.3 |
| 26.4 | 26.4.3.37 |
| 26.5+ | Toutes les versions |
Pourquoi ALIAS, et non MATERIALIZEDLe tableau items offre une vue sur des données déjà présentes dans la colonne Map.
Le stocker deux fois — une fois dans la Map, une fois dans le tableau — doublerait les E/S d’écriture
sans permettre de nouveaux types de requêtes. L’index de texte sur la colonne
ALIAS est
construit lors de l’insert à partir des mêmes données source, donc cette optimisation n’ajoute
au disque que l’empreinte de l’index.Optimisation 4. Modification de la clé primaire
Remarque sur la terminologieDans tout ce document, le terme « clé de tri » est utilisé indifféremment avec « clé primaire ». À strictement parler, ces notions diffèrent dans ClickHouse, mais dans ClickStack, elles renvoient généralement aux mêmes colonnes spécifiées dans la clause
ORDER BY de la table. Pour plus de détails, consultez la documentation ClickHouse sur le choix d’une clé primaire différente de la clé de tri.- Logs (
otel_logs) -(toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp) - Traces (
otel_traces) -(ServiceName, SpanName, toDateTime(Timestamp))
Choisir une clé primaire
Modifier la clé primaire par défautLes clés primaires par défaut suffisent dans la plupart des cas. Les modifications doivent être apportées avec prudence et uniquement avec une bonne compréhension des schémas de requêtes. Modifier une clé primaire peut dégrader les performances d’autres workflows, il est donc indispensable d’effectuer des tests.
- Sélectionnez des colonnes qui correspondent à vos filtres et schémas d’accès habituels. Si vous commencez généralement les investigations d’observabilité en filtrant sur une colonne spécifique, par exemple le nom du pod, cette colonne sera fréquemment utilisée dans les clauses
WHERE. Faites passer ces colonnes avant celles qui sont utilisées moins souvent. - Privilégiez les colonnes qui permettent d’exclure une grande partie du total des lignes lorsqu’un filtre est appliqué, afin de réduire la quantité de données à lire. Les noms de service et les codes de statut sont souvent de bons candidats — dans ce dernier cas, uniquement si vous filtrez sur des valeurs qui excluent la plupart des lignes. Par exemple, filtrer sur les codes 200 correspondra, dans la plupart des systèmes, à la majorité des lignes, contrairement aux erreurs 500, qui ne représenteront qu’un petit sous-ensemble.
- Privilégiez les colonnes susceptibles d’être fortement corrélées avec d’autres colonnes de la table. Cela contribuera à garantir que ces valeurs soient elles aussi stockées de manière contiguë, ce qui améliorera la compression.
- Les opérations
GROUP BY(agrégations pour les graphiques) etORDER BY(tri) sur les colonnes de la clé de tri peuvent gagner en efficacité mémoire.
Modification de la clé primaire
SeverityText avant ServiceName.
Créer une nouvelle table
Clé de tri vs clé primaireNotez que, dans l’exemple ci-dessus, vous devez spécifier une
PRIMARY KEY et un ORDER BY.
Dans ClickStack, elles sont presque toujours identiques.
Le ORDER BY contrôle l’organisation physique des données, tandis que la PRIMARY KEY définit l’index clairsemé.
Dans de rares cas, sur des workloads très volumineux, elles peuvent différer, mais la plupart des utilisateurs devraient les garder alignées.Le rechargement de données historiques dans une nouvelle table vaut rarement la peine à grande échelle. Le coût en compute et en IO est généralement élevé et ne justifie pas les gains de performance. À la place, laissez les données plus anciennes expirer via TTL, tandis que les données plus récentes bénéficient de la clé améliorée.
SeverityText devient la première colonne de la clé primaire, est repris ci-dessous. Dans ce cas, une table est créée pour les nouvelles données, tout en conservant l’ancienne table pour l’analyse historique.
Créer une nouvelle table
Créez la nouvelle table avec la clé primaire souhaitée. Notez le suffixe_23_01_2025 : adaptez-le à la date actuelle. Par exemple :Créer une table Merge
Le moteur Merge (à ne pas confondre avec MergeTree) ne stocke pas lui-même les données, mais permet de lire simultanément depuis n’importe quel nombre d’autres tables.currentDatabase() suppose que la commande est exécutée dans la bonne base de données. Sinon, indiquez explicitement le nom de la base de données.otel_logs.Mettre à jour le ClickStack UI pour lire depuis la table Merge
Configurez le ClickStack UI pour utiliserotel_logs_merge comme table pour la source de données de logs.À ce stade, les écritures continuent d’aller vers otel_logs avec la clé primaire d’origine, tandis que les lectures utilisent la table Merge. Cela n’entraîne aucun changement visible pour les utilisateurs ni aucun impact sur l’ingestion.Échanger les tables
Une instructionEXCHANGE est maintenant utilisée pour permuter atomiquement les noms des tables otel_logs et otel_logs_23_01_2025.otel_logs avec la clé primaire mise à jour. Les données existantes restent dans otel_logs_23_01_2025 et restent accessibles via la table Merge. Le suffixe indique la date à laquelle la modification a été appliquée et correspond au timestamp le plus récent contenu dans cette table.Ce processus permet de modifier les clés primaires sans interruption de l’ingestion et sans impact visible pour les utilisateurs.SeverityNumber doit faire partie de la clé primaire une semaine plus tard, plutôt que SeverityText. Le processus suivant peut être répété autant de fois que nécessaire en cas de modification de la clé primaire.
Créer une nouvelle table
Créez la nouvelle table avec la clé primaire souhaitée. Dans l’exemple ci-dessous,30_01_2025 est utilisé comme suffixe pour indiquer la date de la table. Par exemple :Échanger les tables
Une instructionEXCHANGE est maintenant utilisée pour permuter atomiquement les noms des tables otel_logs et otel_logs_30_01_2025.otel_logs avec la clé primaire mise à jour. Les anciennes données restent dans otel_logs_30_01_2025, accessibles via la table Merge.Tables redondantesSi des politiques TTL sont en place, ce qui est recommandé, les tables avec d’anciennes clés primaires qui ne reçoivent plus d’écritures se videront progressivement à mesure que les données expireront. Elles doivent être surveillées et nettoyées périodiquement une fois qu’elles ne contiennent plus de données. À l’heure actuelle, ce processus de nettoyage est manuel.
Accélération de la recherche de lignes avec les colonnes de bloc
(_block_number, _block_offset) qui l’identifie de manière unique au sein d’une
part. Lorsque vous cliquez sur une ligne de log dans la ClickStack UI pour ouvrir le panneau de détail, ClickStack
exécute une requête complémentaire pour récupérer cette seule ligne. Sans colonnes de bloc, la
clause WHERE de la ligne doit inclure suffisamment de colonnes — généralement la clé primaire
ainsi que Body et SeverityText — pour lever toute ambiguïté. Avec les colonnes de bloc,
la clé primaire, _block_number et _block_offset suffisent. Les grandes
colonnes comme Body ne sont jamais lues pour le lookup, ce qui accélère effectivement la requête.
ClickStack détecte ce réglage à partir de l’instruction CREATE de la table et génère
automatiquement une clause WHERE plus légère lorsque les deux colonnes sont activées. Aucune
modification de la configuration de l’application n’est nécessaire.
Pour activer cette optimisation sur une table de logs ou de traces existante :
ALTER. Les parts existantes continuent
d’utiliser l’ancien mécanisme de recherche ligne par ligne jusqu’à ce qu’une fusion les réécrive.
Optimization 5. Tirer parti des vues matérialisées
Optimisation 6. Exploiter les projections
ORDER BY de la table de base, ce qui permet à ClickHouse d’écarter plus efficacement les données pour les modèles d’accès qui ne correspondent pas à l’ordre initial.
Les vues matérialisées peuvent produire un effet similaire en écrivant explicitement les lignes dans une table cible distincte avec une clé de tri différente. La différence essentielle est que les projections sont maintenues automatiquement et de manière transparente par ClickHouse, tandis que les vues matérialisées sont des tables explicites que ClickStack doit enregistrer et sélectionner délibérément.
Lorsqu’une requête cible la table de base, ClickHouse évalue l’organisation de base et les projections disponibles, échantillonne leurs index primaires, puis sélectionne celle qui peut produire le bon résultat en lisant le moins de granules possible. Cette décision est prise automatiquement par l’analyseur de requêtes.
Dans ClickStack, les projections sont donc surtout adaptées au réordonnancement pur des données, lorsque :
- Les modèles d’accès diffèrent fondamentalement de la clé primaire par défaut
- Il n’est pas pratique de couvrir tous les workflows avec une seule clé de tri
- Vous voulez que ClickHouse choisisse de manière transparente l’organisation physique optimale
Exemple de projections
Utiliser des caractères génériquesDans l’exemple de projection ci-dessus, un caractère générique (
SELECT *) est utilisé. Bien que sélectionner un sous-ensemble de colonnes puisse réduire la surcharge d’écriture, cela limite également les cas dans lesquels la projection peut être utilisée, car seules les requêtes pouvant être entièrement satisfaites par ces colonnes sont éligibles. Dans ClickStack, cela restreint souvent l’utilisation des projections à des cas très spécifiques. C’est pourquoi il est généralement recommandé d’utiliser un caractère générique afin d’en maximiser l’applicabilité.La matérialisation d’une projection peut prendre beaucoup de temps et consommer des ressources importantes. Comme les données d’observabilité expirent généralement via TTL, cela ne doit être fait qu’en cas d’absolue nécessité. Dans la plupart des cas, il suffit de laisser la projection s’appliquer uniquement aux données nouvellement ingérées, afin d’optimiser les intervalles de temps les plus fréquemment interrogés, comme les 24 dernières heures.
SELECT *) et que les filtres de la requête correspondent étroitement au ORDER BY de la projection.
Les requêtes qui filtrent sur TraceId (en particulier avec une égalité) et incluent un intervalle de temps tireraient parti de la projection ci-dessus. Par exemple :
TraceId, ou qui filtrent principalement sur d’autres dimensions qui ne figurent pas en tête de la clé de tri de la projection, n’en tirent généralement aucun bénéfice (et peuvent alors lire via l’organisation de base à la place).
Les projections peuvent aussi stocker des agrégations (comme les vues matérialisées). Dans ClickStack, les agrégations basées sur des projections ne sont généralement pas recommandées, car leur sélection dépend de l’analyseur ClickHouse et leur usage peut être plus difficile à contrôler et à anticiper. Préférez plutôt des vues matérialisées explicites, que ClickStack peut enregistrer et sélectionner de manière intentionnelle au niveau de la couche applicative.
TraceId donné).
Coûts et recommandations
- **Surcoût à l’insert **: une projection
SELECT *avec une clé de tri différente revient, en pratique, à écrire les données deux fois, ce qui augmente les E/S en écriture et peut nécessiter davantage de CPU et de débit disque pour maintenir l’ingestion. - **À utiliser avec parcimonie **: les projections sont surtout utiles pour des modes d’accès réellement différents, lorsqu’un second ordre physique permet un pruning significatif pour une grande part des requêtes, par exemple si deux équipes interrogent le même jeu de données de manière fondamentalement différente.
- **Validez avec des benchmarks **: comme pour tout réglage, comparez la latence réelle des requêtes et l’utilisation des ressources avant et après l’ajout et la matérialisation d’une projection.
Projections légères avec _part_offset
Les projections légères sont en bêta pour ClickStackLes projections légères basées sur
_part_offset ne sont pas recommandées pour les charges de travail ClickStack. Bien qu’elles réduisent le stockage et les E/S en écriture, elles peuvent entraîner davantage d’accès aléatoires à l’exécution des requêtes, et leur comportement en production à l’échelle de l’observabilité est encore en cours d’évaluation. Cette recommandation pourra évoluer à mesure que la fonctionnalité gagnera en maturité et que nous disposerons de plus de données opérationnelles._part_offset vers la table de base, au lieu de dupliquer des lignes complètes. Cela peut réduire considérablement le surcoût de stockage, et de récentes améliorations permettent un élagage au niveau des granules, ce qui les rapproche davantage de véritables index secondaires. Voir :
Alternatives
- de configurer votre collecteur OpenTelemetry pour écrire dans deux tables avec des clés de tri différentes, et de créer des sources ClickStack distinctes pour chaque table.
- de créer une vue matérialisée comme pipeline de copie, c.-à-d. d’attacher une vue matérialisée à la table principale qui redirige les lignes brutes vers une table secondaire avec une clé de tri différente (un modèle de dénormalisation ou de routage). Créez une source pour cette table cible. Vous trouverez des exemples ici.