Corrigé du questionnaire à choix multiple
Réponses et explications des quinze questions de validation des connaissances sur la migration.
Commencez par l'évaluation interactive de la partie 4. Elle ne révèle la bonne réponse et son explication qu'après la soumission des 15 questions. Cette référence conserve les notes techniques plus détaillées pour une révision ultérieure.
| Q | Réponse |
|---|---|
| 1 | B |
| 2 | B |
| 3 | C |
| 4 | C |
| 5 | B |
| 6 | B |
| 7 | B |
| 8 | C |
| 9 | B |
| 10 | C |
| 11 | B |
| 12 | C |
| 13 | B |
| 14 | B |
| 15 | B |
Q1. Correspondance avec le préfixe de la clé de tri — B
L'index primaire de ClickHouse est épars : une entrée par groupe de index_granularity lignes (8192 par défaut). Le moteur s'en sert pour ignorer les granules qui ne peuvent pas contenir de correspondance d'après les premières colonnes de la clé de tri. Une requête qui filtre sur :
- le préfixe complet
(ServiceName, SeverityText, TimestampTime)— les trois colonnes de la clé de tri, avecTimestampTimeen dernier (la colonne finale de plage) — permet à l'index d'éliminer un maximum de granules.
A filtre uniquement sur la dernière colonne : le moteur peut utiliser les valeurs minimale et maximale de TimestampTime par granule, mais il ne peut pas pleinement exploiter l'index épars, car les premières colonnes ne sont pas contraintes. C applique LIKE à une colonne absente de la clé de tri : il faut un index de saut (par exemple tokenbf_v1) ou une analyse complète. D filtre sur une colonne qui ne figure pas du tout dans la clé de tri.
Modèle mental : « Quelle part du préfixe de la clé de tri mon prédicat contraint-il ? »
Q2. Granularité de suppression du TTL — B
SETTINGS ttl_only_drop_parts = 1 est l'élément décisif. Grâce à lui, ClickHouse attend qu'une partie MergeTree entière ait expiré (c'est-à-dire que sa valeur maximale de TimestampDate ait dépassé INTERVAL 30 DAY), puis détache la partie entière du disque. Cette opération coûte considérablement moins cher que le comportement par défaut (réécrire la partie sans les lignes expirées) et s'associe naturellement à un partitionnement par date, dans lequel chaque partition ne contient qu'un petit nombre de parties.
A décrit le comportement du TTL par défaut sans ttl_only_drop_parts = 1. C ne se produirait qu'avec DROP TABLE. D ne correspond à aucun mode réel de ClickHouse.
Modèle mental : le TTL d'une série temporelle doit éliminer des parties, et non des lignes ; assurez-vous que la clé de partition correspond à la limite du TTL.
Q3. Pertinence de LowCardinality — C
LowCardinality(String) crée un dictionnaire propre à la colonne, qui associe chaque chaîne distincte à un petit code entier, puis stocke ces codes. La compression est exceptionnelle lorsqu'il n'existe que très peu de valeurs distinctes (par exemple ServiceName, SeverityText, Region, EnvironmentName). Le seuil de rentabilité observé se situe autour de 10 000 valeurs distinctes ; au-delà, le dictionnaire commence à dominer le stockage et le coût des recherches.
A annulerait tout l'intérêt du dictionnaire (chaque ligne reçoit un nouveau code). B est un cas d'école pour le stockage plein texte assorti d'un index de saut. D convient aux types Date/DateTime, et non aux chaînes ; les types temporels natifs de ClickHouse sont déjà efficaces.
Q4. Rôle du moteur Null — C
Le motif otel_logs (Null engine) → otel_logs_mv → otel_logs_v2 (MergeTree) est un idiome ClickHouse classique : il permet d'accepter des insertions conformes au schéma OTel brut (que l'OTel Collector sait écrire), tout en ne conservant que le schéma transformé que l'on souhaite réellement interroger (avec des colonnes matérialisées pour GeoCountry, BrowserFamily, RequestPage, etc.). Sans la table Null, il faudrait soit (a) stocker les données deux fois, soit (b) apprendre à l'OTel Collector votre schéma cible sur mesure, ce qui lui ferait perdre sa simplicité d'intégration.
A/B/D ne justifient pas ce choix. Le collecteur peut écrire directement dans MergeTree, mais le schéma du collecteur et le schéma de stockage seraient alors couplés, précisément ce que nous voulons éviter.
Q5. Emplacement des attributs dans OTel — B
Le modèle de données OpenTelemetry sépare les attributs en deux groupes :
- Attributs de ressource — décrivent l'entité qui produit la télémétrie (hôte, conteneur, service, pod k8s). Définis une fois lors de l'initialisation du SDK, puis propagés automatiquement.
- Attributs de span, de journal ou de métrique — décrivent un événement donné. Ils varient d'un enregistrement de signal à l'autre.
service.name, service.version, host.name, cloud.region, k8s.pod.name sont tous des attributs de ressource. Dans l'atelier, la colonne ServiceName est une promotion de premier niveau de ResourceAttributes['service.name'], afin de pouvoir servir de première colonne de la clé de tri.
Q6. Index de saut pour les filtres hors préfixe — B
Un index de saut complète la clé primaire avec des indications par granule du type « ce granule ne peut pas contenir de correspondance », pour les colonnes qui ne figurent pas dans la clé de tri. bloom_filter(0.01) est le choix habituel pour les colonnes à forte cardinalité faisant l'objet de recherches d'égalité (TraceId, UserId, RequestId).
A nuirait en réalité aux requêtes de recherche : avec des granules plus grossiers, chaque « saut » manqué couvre davantage de données. C est impossible sans reconstruire toute la table (ORDER BY est immuable dans MergeTree). D ne modifie pas les plans de requête.
Modèle mental : « clé de tri pour le motif d'accès dominant ; index de saut pour les autres ».
Q7. Pourquoi la double écriture s'effectue dans le collecteur — B
Lorsque les deux systèmes cibles sont alimentés par le même producteur (le collecteur émet des lots identiques vers ES et CH), les comparaisons d'équivalence isolent les différences de stockage et de moteur de requête. Avec une double lecture au niveau du tableau de bord, il faudrait au contraire intégrer l'orchestration des requêtes entre systèmes au tableau de bord ; il serait impossible de savoir si les différences proviennent du parcours de stockage ou du propre raisonnement du tableau de bord, et le retour arrière serait plus complexe (il faudrait rétablir le code du tableau de bord, et non simplement retirer un exportateur du collecteur).
A n'est pas pertinent. C est faux (Grafana, HyperDX et même Kibana au moyen de clusters distants peuvent lire plusieurs sources). D est tout simplement incorrect.
Q8. Rôle d'AggregatingMergeTree — C
Il est utile de bien comprendre ce point, car c'est ici que ClickHouse prend clairement l'avantage sur le motif de transformation d'Elasticsearch.
Dans les transformations Elasticsearch, les événements bruts sont périodiquement analysés et réagrégés dans un index de synthèse. Le coût augmente avec le volume brut.
Dans AggregatingMergeTree de ClickHouse :
- Chaque insertion produit un petit état partiel ; par exemple,
countState()n'est qu'un entier, tandis qu'avgState()est une paire (somme, nombre). - Les fusions en arrière-plan combinent les états partiels de parties adjacentes (la propriété algébrique du nombre, de la somme, etc. rend cette opération triviale ; même des représentations de centiles comme t-digest peuvent être fusionnées).
- Les requêtes combinent les états partiels restants avec les agrégats
*Merge.
Les lignes brutes ne sont jamais relues après le calcul de l'état initial. Le coût de l'agrégation est payé à l'insertion, et non pendant la requête, et il n'augmente pas avec la durée de conservation.
Q9. Pourquoi les dictionnaires IP_TRIE sont rapides — B
Les dictionnaires sont des objets de première classe dans ClickHouse et résident dans la mémoire du collecteur après SYSTEM RELOAD DICTIONARY. La disposition IP_TRIE est spécifiquement un arbre de préfixes (un trie indexé par préfixe binaire) ; une recherche CIDR s'effectue donc en O(prefix-length), soit environ 32 comparaisons de bits pour IPv4. Aucun disque, aucune analyse, aucun coût par ligne au-delà du parcours de l'arbre.
A est faux (les dictionnaires sont accessibles au planificateur ; ils disposent simplement de parcours rapides spécialisés). C est invraisemblable avec les tailles du jeu de données MaxMind (environ 3 Mo pour la base des pays). D est vrai, mais n'explique pas la rapidité.
Q10. Équivalence d'ILM — C
L'architecture de stockage de ClickHouse Cloud élimine l'essentiel d'ILM :
- Aucun niveau de nœuds — toutes les données résident dans un stockage objet assorti d'une mise en cache locale automatique. Aucune allocation « chaud ou tiède » à gérer.
- Aucun roulement — une seule table MergeTree partitionnée par date remplace le motif des index successifs.
- Aucune planification de fusion forcée — les fusions en arrière-plan sont continues et s'ajustent automatiquement.
- Le TTL gère la suppression — une seule clause remplace toute la phase de suppression à 30 jours.
Il s'agit de l'un des messages les plus importants de toute discussion sur une migration d'ES vers CH. La complexité opérationnelle du client diminue d'un ordre de grandeur.
Q11. Pourquoi filtrer sur SpanKind = 'Server' — B
C'est le piège rencontré dans l'exercice 1. Les spans EventStream en interrogation longue de flagd dans la démo OTel durent volontairement environ 10 minutes : ils représentent la gestion des connexions en continu, et non des requêtes visibles par l'utilisateur. Ils dominent toute requête naïve portant sur les « 10 opérations les plus lentes ».
En production, le même motif apparaît dans tout système comportant des RPC de longue durée (SignalR, diffusion en continu du serveur gRPC, abonnements GraphQL, connexions persistantes MQTT). Pour rechercher la latence ressentie par l'utilisateur, filtrez toujours sur SpanKind = 'Server' (requêtes entrantes) ou sur un motif SpanName particulier.
Modèle mental : les spans de type serveur représentent « ce que l'utilisateur a attendu ». Les types Internal/Client/Producer/Consumer représentent « ce qui s'est produit en dessous ».
Q12. Indexation pour la recherche plein texte — C
L'index text constitue l'approche moderne recommandée. Il est devenu disponible pour tous dans ClickHouse 26.2 et le propre schéma otel_logs_v2 de l'atelier l'utilise sur la colonne Body :
INDEX idx_body Body TYPE text(tokenizer='sparseGrams') GRANULARITY 8Propriétés essentielles de text :
- Découpage en lexèmes configurable —
tokens(espaces),ngrams(N)(sous-chaînes),sparseGrams(lexèmes intelligents de longueur variable, valeur par défaut de l'atelier) ou une expression régulière personnalisée. - Meilleure précision que
tokenbf_v1— au lieu d'une approximation par filtre de Bloom pour chaque granule, l'indextextconserve une véritable liste de publications par lexème. Le taux de faux positifs lors d'une requête est proche de 0, contre la valeur par défaut~0.025de tokenbf_v1. - Gestion correcte de la casse et prise en charge du CJK dans les analyseurs plus récents ; tokenbf_v1 ne comprenait pas la segmentation Unicode.
B (tokenbf_v1) fonctionne toujours et constitue la bonne réponse pour les versions de ClickHouse antérieures à 26.2 ; la question demande volontairement la recommandation moderne. De nombreux déploiements de production existants utilisent encore tokenbf_v1 ; les deux index peuvent coexister sur la même colonne pendant un roulement. A (minmax) suit les valeurs minimale et maximale par granule : il convient aux colonnes numériques ou de date monotones, et non au texte. D (bloom_filter sans découpage en lexèmes) traite la chaîne entière comme un seul lexème ; il n'aide que pour une égalité sur toute la chaîne (par exemple WHERE Body = 'an exact full string'), jamais pour une recherche de sous-chaîne.
Modèle mental : pour les sous-chaînes ou le plein texte dans des colonnes String : text à partir de la version 26.2, tokenbf_v1 sur un cluster plus ancien. Pour l'égalité exacte sur une chaîne à forte cardinalité (par exemple TraceId) : bloom_filter.
Q13. Pourquoi ClickHouse ne fournit pas un index inversé semblable à celui d'ES — B
Il faut parler de « choix de conception différent », et non de « fonctionnalité manquante ». Les deux moteurs acceptent des prédicats plein texte, mais ils sont optimisés pour des motifs d'accès distincts.
Index inversé d'Elasticsearch
Fondé sur Lucene, il constitue le parcours d'accès principal pour le texte :
- Granularité : par document. L'index stocke
token → posting list of (doc_id, term_frequency, positions)pour chaque document qui contient le lexème. - Objectif : trouver les documents correspondant à une requête. Il renvoie les documents effectivement correspondants, et non un ensemble candidat.
- Classement intégré : calcul des scores BM25 / TF-IDF pendant la recherche ; les résultats sont préclassés par pertinence.
- Obligatoire par défaut : chaque champ
texten reçoit un ; impossible d'avoir un champ de texte sans payer le coût de l'index. - Coût de stockage : généralement 30 à 50 % de la taille du document d'origine.
- Sémantique de requête riche : phrase avec tolérance de distance, correspondance approximative avec distance de Levenshtein, pondération multichamp, DSL de notation ; tout cela est possible grâce à la granularité par document.
Index text de ClickHouse (disponible pour tous depuis la version 26.2)
Un index de saut ajouté au stockage colonnaire, et non le parcours d'accès principal :
- Granularité : par granule (8192 lignes par défaut). Il indique au moteur que « ce granule pourrait contenir des lignes avec le lexème X », sans identifier les lignes correspondantes.
- Objectif : éliminer des granules d'une analyse. Après cette élimination, ClickHouse lit les granules candidats et applique le prédicat
LIKEligne par ligne. - Aucun classement par pertinence. Les résultats sont renvoyés dans l'ordre du stockage. Ni BM25, ni TF-IDF, ni notion de résultat « plus pertinent ».
- Facultatif : activation volontaire par DDL sur une colonne. Les tables qui n'en comportent pas analysent simplement la colonne.
- Coût de stockage : environ 1 à 5 % de la taille de la colonne, avec une liste de publications par granule et non par ligne.
- Sémantique de requête : SQL
LIKE,ILIKE,hasToken, expressions régulières. Ni phrase avec tolérance de distance, ni correspondance approximative, ni DSL de notation.
Parcours de WHERE Body LIKE '%timeout%' sur 1 milliard de lignes
| Étape | Elasticsearch | ClickHouse |
|---|---|---|
| 1 | Découper 'timeout' en lexèmes → [timeout] | Identique |
| 2 | Rechercher le lexème dans l'index inversé → liste de publications des identifiants de documents (par exemple 2,3 millions de documents) | Parcourir l'index de saut : par exemple, 5 000 granules sur 122 000 peuvent contenir timeout, et les 117 000 autres sont ignorés |
| 3 | Récupérer chaque document correspondant, calculer son score et renvoyer les résultats classés | Lire les colonnes des 5 000 granules candidats (environ 41 millions de lignes) |
| 4 | (aucune) | Appliquer LIKE '%timeout%' ligne par ligne et obtenir environ 2,3 millions de correspondances |
| Coût dominant | Parcours de la liste de publications | Analyse des lignes dans les granules candidats (environ 24 fois moins de données que sans index) |
Situations favorables à chacun
| Cas d'usage | Mieux adapté | Pourquoi |
|---|---|---|
| Recherche de produits d'e-commerce (« chaussures de course entre 40 et 80 dollars ») | ES | Le classement par pertinence est central ; les requêtes renvoient de petits ensembles classés |
| Recherche dans la documentation au fil de la saisie | ES | Correspondance approximative, requêtes de phrase, saisie semi-automatique |
| Analyse de journaux (« erreurs 5xx mentionnant “timeout” au cours de la dernière heure ») | CH | Filtrage puis agrégation sur des milliards de lignes ; le classement est sans intérêt |
| Analyse de sécurité (« alerter sur la chaîne “malicious_pattern” ») | CH | L'analyse avec élimination convient parfaitement |
| « 100 journaux les plus pertinents pour une requête en texte libre » | ES | CH ne calcule aucun score ; il faudrait écrire son propre classement SQL |
« Somme de bytes_transferred quand le chemin correspond à /api/.* sur 90 j » | CH | L'agrégation domine ; le prédicat de texte n'est qu'un filtre |
Résumé à présenter au client
Les deux systèmes savent trouver les lignes contenant 'timeout'. La requête suivante révèle la différence de conception :
- Après avoir trouvé les correspondances, la suite naturelle pour ES est « classer par pertinence ».
- Après avoir trouvé les correspondances, la suite naturelle pour ClickHouse est « calculer maintenant la latence p99 de ces lignes, regroupée par service ».
Une même disposition physique des données ne peut être optimale pour les deux questions. ES privilégie les documents et le classement ; ClickHouse, le stockage colonnaire et l'analyse.
Q14. Stratégie d'évolution du schéma — B
Cette réponse provient directement du corrigé de la décision 3 de l'ADR de la partie 2 :
- Map par défaut préserve la flexibilité : toute nouvelle clé d'attribut arrive simplement dans
LogAttributes, sans DDL. - Promotion des clés fréquentes vers des colonnes dédiées lorsque l'utilisation justifie la modification du schéma (règle indicative : au moins 30 % des lignes référencent la clé et au moins 2 tableaux de bord/alertes l'utilisent).
- La promotion ne détruit rien :
ALTER TABLE ... ADD COLUMN MATERIALIZED LogAttributes['X']permet aux lignes anciennes et nouvelles de coexister.
A (colonne JSON sans promotion) apporte de la souplesse, mais aucun parcours rapide ; les mesures montrent une exécution environ deux fois plus lente que Map au volume de cet atelier. C (« une colonne par attribut possible ») échoue dès qu'un service émet un nouvel attribut. D n'est pas une migration.
Q15. Compromis de l'échantillonnage — B
L'échantillonnage en aval reporte la décision de conserver ou d'éliminer la trace jusqu'à la réception de tous ses spans. Il peut donc s'appuyer sur des signaux à l'échelle de la trace (un span en erreur ? un span à forte latence ?) pour décider. tail_sampling_processor d'OpenTelemetry est l'implémentation canonique ; elle exige en contrepartie une mise en mémoire tampon et ajoute de la complexité dans le collecteur.
A (échantillonnage en amont) décide au début de la trace, avant qu'une erreur ne se soit produite ; il est incompatible avec l'exigence de « conserver toutes les traces en erreur ». C (par span) rompt la cohérence de la trace (la moitié de ses spans pourraient disparaître). D (aucun échantillonnage) est rarement viable à 1 million de traces/s.
En production, le choix approprié est généralement un échantillonneur en aval avec des règles telles que « conserver 100 % des traces en erreur, 5 % des traces lentes sans erreur et 0,1 % des traces rapides sans erreur ».
Guide d'interprétation du score
| Score | Signification |
|---|---|
| 14–15 | Vous avez assimilé le fonctionnement de ClickHouse comme celui d'OTel. Vous êtes prêt à piloter une migration client. |
| 11–13 | Réussite, mais relisez les explications des questions manquées. Les questions sur le schéma et le modèle de données sont celles qui prédisent le mieux la réussite des échanges avec le client. |
| 8–10 | Résultat limite. Relisez les corrigés de la partie 2 : ces exercices couvrent directement la plupart des concepts. |
| ≤ 7 | Reprenez les points pédagogiques concernés avant de tenter le QCM de mise en situation. |