L'importance des opérateurs structurels dans TraceQL
Contrairement aux langages de requête traditionnels qui se limitent au filtrage d'attributs, TraceQL, le langage de requête de Grafana Tempo, permet d'interroger la topologei même d'une trace distribuée. Dans un système complexe, une requête utilisateur traverse de multiples micro-services, créant une hiérarchie de "spans" (unités de travail). Les opérateurs structurels permettent de répondre à des questions portant sur l'architecture de l'appel : "Quels services de base de données sont appelés par mon frontend ?", "Quel est le dernier maillon d'une chaîne d'erreurs en cascade ?" ou "Quels sont les spans frères d'un processus spécifique ?".
Une règle fondamentale de TraceQL (hors variantes d'union) est que seuls les spans correspondant à la condition de droite sont retournés. La condition de gauche sert de point de référence pour établir la relation hiérarchique.
Opérateurs de hiérarchie de base
Le descendant (>>)
Cet opérateur recherche les spans (à n'importe quel niveau de profondeur) qui descendent d'un span parent spécifique.
{ resource.service.name = "api-gateway" } >> { db.system = "mysql" }
Cette requête identifie tous les appels MySQL qui ont été déclenchés, directement ou indirectement, par le service api-gateway.
L'ancêtre (<<)
Il fonctionne à l'inverse du descendant : il recherche les spans qui se situent au-dessus d'un span cible dans la hiérarchie.
{ status = error } << { span.kind = server }
Ici, on cherche les spans de type serveur qui sont les ancêtres d'une erreur. Cela permet de remonter à la source d'une propagation de panne.
L'enfant direct (>)
Moins large que le descendant, cet opérateur se limite au premier niveau de profondeur.
{ http.method = "POST" } > { otel.library.name = "moped" }
Cela retournera uniquement les spans utilisant la bibliothèque "moped" qui sont des enfants directs d'une requête HTTP POST.
Le parent direct (<)
Il identifie le span qui a immédiatement engendré le span cible.
{ db.operation = "find" } < { service.name = "user-service" }
Cette requête trouve les spans du service user-service qui sont les parents immédiats d'une opération de recherche en base de données.
Le frère (~)
Deux spans sont considérés comme frères s'ils partagent le même parent direct.
{ app.component = "cache" } ~ { app.component = "validator" }
Cela permet de voir si un composant de validation a été exécuté en parallèle ou dans la même unité de travail qu'un composant de cache.
Opérateurs d'Union (&)
Les opérateurs de base cachent le point de référence. Si vous avez besoin de visualiser à la fois la cause et l'effet (les deux côtés de la relation), utilisez les variantes d'union : &>>, &<<, &>, &<, &~.
{ http.target = "/login" && status = error } &>> { status = error }
Cette requête retournera non seulement le span d'erreur au point d'entrée "/login", mais aussi tous les spans d'erreur descendants, offrant une vue complète de l'échec de la transaction.
Opérateurs de négation expérimentaux (!)
TraceQL propose des opérateurs pour exclure des relations structurelles spécifiques. Bien qu'expérimentaux, ils sont puissants pour isoler des comportements atypiques.
!>>: N'est pas un descendant de...!>: N'est pas un enfant direct de...!<: N'est pas un parent de...!~: N'est pas un frère de...
Cas pratique : Trouver les spans feuilles (sans enfants)
{ } !< { resource.service.name = "order-processor" }
En cherchant des spans du service order-processor qui "ne sont les parents de rien", on obtient les points terminaux de l'exécution.
Optimisation et performance des requêtes
L'exécution des opérateurs structurels repose sur une comparaison intensive des ensembles de spans (spansets) au sein de l'arbre de trace. Dans le code source de Tempo (notamment pkg/traceql/ast_execute.go), ces opérations nécessitent des jointures coûteuses en mémoire et en CPU pour les traces volumineuses.
Pour optimiser vos recherches :
- Filtrez d'abord par attributs : Réduisez la taille des spansets de gauche et de droite avant d'appliquer l'opérateur structurel.
- Utilisez les opérateurs logiques (&&, ||) : Si vous voulez simplement savoir si deux conditions existent dans la même trace sans lien hiérarchique précis, les opérateurs logiques sont nettement plus performants que les opérateurs structurels.
- Évitez les conditions vides : Une condition
{ }à gauche d'un opérateur structurel force le moteur de recherche à évaluer l'intégralité des spans de la trace comme points de référence potentiels.
Analyse de flux complexes
Les opérateurs structurels permettent de détecter des configurations incorrectes ou des fuites entre environnements. Par exemple, pour identifier une trace qui aurait traversé par erreur à la fois un environnement de production et de test :
{ deployment.env = "prod" } && { deployment.env = "staging" }
Bien que cet exemple utilise l'opérateur logique && au niveau de la trace, on peut affiner la recherche structurelle pour voir si un service de production appelle spécifiquement un service de staging :
{ deployment.env = "prod" } >> { deployment.env = "staging" }
Cette précision transforme le débogage réactif en une analyse proactive de l'architecture du système.