Adrienne Vermorel
Définir des métriques dans dbt : bonnes pratiques et patterns
Vous avez configuré vos semantic models dans dbt avec les entities, dimensions et measures. Reste maintenant l’étape qui bloque la plupart des équipes : écrire des métriques qui fonctionnent réellement.
La bonne nouvelle ? La conception de métriques suit des patterns prévisibles. Une fois les cinq types de métriques et quelques principes d’organisation assimilés, vous pouvez exprimer la quasi-totalité de vos calculs métier. Ce tutoriel passe en revue chaque type de métrique avec des exemples concrets, puis aborde les conventions de nommage et les patterns d’organisation qui gardent vos métriques maintenables à mesure que votre projet grandit.
Les cinq types de métriques
MetricFlow prend en charge cinq types de métriques. Chacun a un rôle précis, et choisir le bon détermine si votre calcul est correct.
Simple metrics
Les simple metrics référencent une seule measure avec une agrégation. Ce sont les briques de base de tout le reste.
metrics: - name: order_total label: "Total Order Value" description: "Sum of all order values" type: simple type_params: measure: order_valueC’est tout. La measure order_value définit déjà son agrégation (probablement sum), donc la métrique ne fait que la référencer. Utilisez les simple metrics pour les comptages et sommes de base : revenu total, nombre de commandes, utilisateurs actifs.
Cumulative metrics
Les cumulative metrics agrègent sur des fenêtres temporelles. Pensez aux utilisateurs actifs hebdomadaires, au revenu month-to-date ou aux moyennes glissantes sur 30 jours.
metrics: - name: weekly_active_users label: "Weekly Active Users" description: "Unique users active in the past 7 days" type: cumulative type_params: measure: active_users window: 7 daysDeux paramètres contrôlent le comportement :
windowcrée une fenêtre glissante (7 jours, 30 jours)grain_to_daterepart de zéro aux limites de période (month-to-date, year-to-date)
metrics: - name: mtd_revenue label: "Month-to-Date Revenue" type: cumulative type_params: measure: revenue grain_to_date: monthUn prérequis surprend souvent : les requêtes utilisant des cumulative metrics avec window doivent inclure metric_time comme dimension. Sans dimension temporelle, la fenêtre glissante n’a pas de point d’ancrage.
Derived metrics
Les derived metrics effectuent des calculs à partir d’autres métriques. Elles sont indispensables pour les marges, les taux de croissance et les comparaisons période sur période.
metrics: - name: gross_profit label: "Gross Profit" type: derived type_params: expr: revenue - cost_of_goods_sold metrics: - name: revenue - name: cost_of_goods_soldLe paramètre expr accepte toute expression SQL valide utilisant les noms des métriques référencées. Pour les calculs période sur période, utilisez offset_window avec un alias :
metrics: - name: revenue_growth_wow label: "Revenue Growth % W/W" type: derived type_params: expr: (revenue - revenue_last_week) / revenue_last_week * 100 metrics: - name: revenue - name: revenue offset_window: 7 days alias: revenue_last_weekL’alias permet de référencer la même métrique à différents décalages temporels dans une seule expression.
Ratio metrics
Les ratio metrics divisent un numérateur par un dénominateur. Pourquoi ne pas utiliser une derived metric ? Parce que les ratios posent un piège mathématique : la somme des ratios n’est pas le ratio des sommes.
Si le magasin A a un taux de conversion de 50 % et le magasin B de 25 %, le taux combiné n’est pas 37,5 %. Il dépend du volume de chaque magasin. Les ratio metrics gèrent cela correctement en sommant numérateur et dénominateur séparément avant de diviser.
metrics: - name: conversion_rate label: "Conversion Rate" type: ratio type_params: numerator: conversions denominator: sessionsVous pouvez appliquer des filtres uniquement au numérateur ou au dénominateur :
metrics: - name: mobile_conversion_rate label: "Mobile Conversion Rate" type: ratio type_params: numerator: name: conversions filter: - "{{ Dimension('session__device_type') }} = 'mobile'" denominator: sessionsConversion metrics
Les conversion metrics suivent le parcours d’un événement de base vers un événement de conversion dans une fenêtre temporelle. Pensez analyse de funnel : visites vers achats, inscriptions vers activations.
metrics: - name: visit_to_purchase_rate label: "Visit to Purchase Rate" type: conversion type_params: entity: user calculation: conversion_rate base_measure: visits conversion_measure: purchases window: 7 daysLe paramètre entity définit la clé de jointure entre les événements de base et de conversion. Le paramètre calculation peut être conversion_rate (pourcentage) ou conversions (comptage).
Pour un matching plus strict, constant_properties garantit la correspondance des attributs entre les événements :
type_params: entity: user calculation: conversion_rate base_measure: visits conversion_measure: purchases window: 7 days constant_properties: - base_property: "{{ Dimension('visit__device_type') }}" conversion_property: "{{ Dimension('purchase__device_type') }}"Ici, une conversion n’est comptée que si le type d’appareil de l’utilisateur correspond entre la visite et l’achat.
Des conventions de nommage qui passent à l’échelle
Un nommage cohérent rend les métriques faciles à trouver.
Names vs labels
Le champ name est destiné au code. Le champ label est destiné aux humains. Gardez-les distincts :
name: revenue_growth_momlabel: "Revenue Growth % M/M"Les names utilisent le snake_case, tout en minuscules. Les labels utilisent les majuscules appropriées et peuvent inclure des symboles comme %.
Patterns par type de métrique
Chaque type de métrique bénéficie d’un pattern de nommage différent :
| Type | Pattern | Exemple |
|---|---|---|
| Simple | {nom}_{agrégation} | order_count, revenue_sum |
| Cumulative | {période}_{métrique} | weekly_active_users, mtd_revenue |
| Derived | {métrique}_growth_{période} | revenue_growth_mom, orders_growth_yoy |
| Ratio | {numérateur}_per_{dénominateur} | revenue_per_customer, orders_per_session |
| Conversion | {action}_to_{action}_rate | visit_to_buy_rate, signup_to_activate_rate |
Soyez précis
Des noms vagues créent de la confusion. revenue peut désigner le revenu brut, net ou ajusté. response_time peut être en millisecondes ou en secondes.
Mieux :
gross_revenuenet_revenue_after_refundsresponse_time_secondsresponse_time_p95_ms
Regroupez les métriques liées
Utilisez des préfixes cohérents pour regrouper les métriques apparentées :
# Famille revenuerevenue_totalrevenue_per_orderrevenue_growth_momrevenue_mtd
# Famille customercustomer_countcustomer_lifetime_valuecustomer_acquisition_costcustomer_retention_rateQuand quelqu’un cherche “revenue”, il trouve toutes les métriques liées au revenu au même endroit.
Organiser les métriques dans les grands projets
Les petits projets peuvent définir semantic models et métriques dans le même fichier. Les grands projets ont besoin de plus de structure.
Structure co-localisée
Pour les projets de moins de 20 métriques, gardez tout ensemble :
models/ marts/ mrt__finance__orders.sql mrt__finance__orders.yml # semantic model + métriques mrt__sales__customers.sql mrt__sales__customers.yml # semantic model + métriquesLe fichier YAML contient à la fois la définition du semantic model et les métriques construites à partir de celui-ci.
Structure en sous-dossiers parallèles
Pour les projets plus importants, séparez les semantic models des métriques et organisez par domaine :
models/ marts/ mrt__finance__orders.sql mrt__sales__customers.sql semantic_models/ orders.yml customers.yml metrics/ revenue_metrics.yml customer_metrics.yml conversion_metrics.ymlCette structure passe à l’échelle parce que les métriques couvrent souvent plusieurs semantic models. Une métrique customer_lifetime_value peut référencer des measures provenant des semantic models orders et customers. Un dossier dédié aux métriques évite les choix de placement arbitraires.
Une seule primary entity par semantic model
Chaque semantic model doit avoir exactement une primary entity. Cette contrainte garde le graphe sémantique navigable.
semantic_models: - name: orders defaults: agg_time_dimension: ordered_at model: ref('mrt__finance__orders') entities: - name: order type: primary - name: customer type: foreign - name: product type: foreignLa primary entity (order) identifie ce que chaque ligne représente. Les foreign entities (customer, product) permettent les jointures vers d’autres semantic models.
Patterns de métriques avancés
Les métriques du monde réel nécessitent plus que de simples agrégations.
Comparaisons période sur période
Le paramètre offset_window décale une métrique dans le passé :
metrics: - name: bookings_vs_last_week label: "Bookings Change vs Last Week" type: derived type_params: expr: bookings - bookings_7_days_ago metrics: - name: bookings - name: bookings offset_window: 7 days alias: bookings_7_days_agoPour un changement en pourcentage :
expr: (bookings - bookings_7_days_ago) / NULLIF(bookings_7_days_ago, 0) * 100Le NULLIF empêche la division par zéro quand la semaine précédente n’avait aucune réservation.
Métriques filtrées
Appliquez des filtres avec le templating Jinja :
metrics: - name: enterprise_revenue type: simple type_params: measure: revenue filter: - "{{ Dimension('customer__segment') }} = 'enterprise'"Pour les dimensions temporelles avec une granularité spécifique :
filter: - "{{ TimeDimension('order__ordered_at', 'month') }} >= '2024-01-01'"Gestion des nulls dans les séries temporelles
Les métriques sans données pour une période retournent null, ce qui crée des trous dans les graphiques. Deux paramètres corrigent cela :
type_params: measure: name: revenue fill_nulls_with: 0 join_to_timespine: truejoin_to_timespine: true garantit que chaque date apparaît dans les résultats. fill_nulls_with: 0 remplace les nulls par des zéros. Ensemble, ils produisent des séries temporelles complètes, sans trous.
Tests et validation
MetricFlow valide les configurations à trois niveaux :
- Validation du parsing : le YAML respecte-t-il le schéma ?
- Validation sémantique : les noms sont-ils uniques ? Les références existent-elles ? Y a-t-il exactement une primary entity ?
- Validation de la plateforme : les colonnes référencées existent-elles dans les tables physiques ?
Lancez toutes les validations avec :
# dbt Clouddbt sl validate
# dbt Coremf validate-configsAjoutez --verbose-issues --show-all pour un output détaillé lors du débogage.
Intégration CI
Ajoutez la validation à votre pipeline CI pour détecter les changements cassants :
- name: Validate semantic layer run: dbt sl validateCela empêche de merger des PRs qui cassent les définitions de métriques.
Anti-patterns à éviter
Modèles ad hoc pour les métriques
Ne créez pas un nouveau modèle dbt juste pour définir une métrique :
-- Mauvais : models/metrics/monthly_revenue.sqlSELECT DATE_TRUNC('month', ordered_at) AS month, SUM(amount) AS monthly_revenueFROM {{ ref('mrt__finance__orders') }}GROUP BY 1Cela duplique la logique de transformation. Définissez plutôt la métrique sur votre modèle mart existant et laissez MetricFlow gérer l’agrégation.
Somme de ratios
Ne faites jamais la moyenne de pourcentages ou de taux :
# Faux : donnera des résultats mathématiquement incorrects- name: avg_conversion_rate type: simple type_params: measure: conversion_rate # C'est déjà un pourcentage par ligneUtilisez une ratio metric avec des measures séparées pour le numérateur et le dénominateur. Le semantic layer calcule le ratio après avoir agrégé les composants.
Filtres en dur dans les measures
Les measures avec des filtres intégrés réduisent la flexibilité :
# Rigidemeasures: - name: enterprise_revenue expr: CASE WHEN segment = 'enterprise' THEN amount END agg: sumMieux vaut définir une measure générale et appliquer les filtres au niveau de la métrique :
measures: - name: revenue expr: amount agg: sum
metrics: - name: enterprise_revenue type: simple type_params: measure: revenue filter: - "{{ Dimension('customer__segment') }} = 'enterprise'"Vous pouvez ensuite créer smb_revenue, startup_revenue ou n’importe quel autre segment sans définir de nouvelles measures.
Descriptions manquantes
Des métriques sans description deviennent des mystères :
# Que mesure-t-on ici ?- name: arr type: simple type_params: measure: arr_valueIncluez toujours description et label :
- name: arr label: "Annual Recurring Revenue" description: "Sum of annualized contract values for active subscriptions, excluding one-time fees" type: simple type_params: measure: arr_valueDans six mois, quelqu’un vous remerciera.
Et ensuite
Les simple metrics sur vos indicateurs métier principaux (revenu, commandes, utilisateurs) sont le bon point de départ. Faites-les fonctionner dans vos outils BI avant d’ajouter de la complexité.
Ensuite, une seule derived metric comme une comparaison période sur période ou un taux de croissance vous fera pratiquer le pattern offset_window, qui couvre la majorité des besoins de reporting.
Vos modèles dbt existants contiennent probablement de la logique métier qu’il vaut la peine de migrer : filtres en dur, pourcentages calculés et tables pré-agrégées sont autant de candidats pour de véritables métriques dans le semantic layer.
Inutile de définir toutes les métriques possibles dès le départ. Quelques exemples bien structurés donneront à votre équipe des patterns à suivre au fil des nouveaux besoins.