Observabilité des données
Résumé
L’observabilité des données réduit le data downtime, le temps pendant lequel une donnée est fausse, absente, imprécise ou inaccessible. Le monitoring détecte qu’un seuil est franchi ; l’observabilité ajoute assez de contexte pour comprendre pourquoi et où. Six angles de lecture la structurent : fraîcheur, volume, distribution, schéma, lineage et qualité. Chacun répond à une question simple, et la plupart se mesurent avec une requête SQL.
Prérequis
Idempotence et backfill (relances et tranches de données) et dbt — Fondamentaux (tests, sources). Le vocabulaire SLI, SLO, SLA est défini en cours de note.
Le problème : le data downtime
Une donnée peut être fausse sans qu’aucune alerte ne sonne : le pipeline a réussi, les tables existent, mais le contenu a dérivé. Le coût est triple : du temps passé à chercher la cause plutôt qu’à la corriger, de la confiance perdue chez les consommateurs, et des décisions prises sur des chiffres dégradés. Observer, c’est raccourcir le délai entre l’incident et sa compréhension.
Monitoring ou observabilité
| Monitoring | Observabilité | |
|---|---|---|
| Question | Un seuil est-il franchi ? | Pourquoi, et où ? |
| Approche | Règles connues d’avance : ça marche ou non | Signaux croisés et contexte |
| Exemple | « Ce modèle a échoué. » | « Ce modèle a échoué parce que sa source n’a reçu aucune ligne depuis trois heures. » |
Les deux se complètent : le monitoring donne le signal, l’observabilité donne la piste.
Six angles de lecture
Les formulations d’angles varient selon les auteurs. Selon un billet de Monte Carlo, la catégorie a d’abord été définie par cinq piliers : fraîcheur, qualité, volume, schéma et lineage ; d’autres présentations citent la distribution des valeurs. Ce vault retient six angles, en distinguant la distribution (la forme statistique des valeurs) de la qualité (des règles métier vérifiées explicitement).1
flowchart LR S["Source"] --> P["Pipeline"] --> T["Table"] --> C["Consommateur"] F["Fraîcheur : arrive-t-elle à temps ?"] -.-> S V["Volume : combien de lignes ?"] -.-> T D["Distribution : les valeurs dérivent-elles ?"] -.-> T SC["Schéma : la structure a-t-elle changé ?"] -.-> S Q["Qualité : les règles métier tiennent-elles ?"] -.-> T L["Lineage : d'où vient le problème, qui est touché ?"] -.-> P
| Angle | Question | Signal typique |
|---|---|---|
| Fraîcheur | La donnée attendue est-elle disponible à temps ? | Délai depuis le dernier chargement |
| Volume | Le nombre de lignes change-t-il brutalement ? | Chute ou explosion par rapport à l’habitude |
| Distribution | Les valeurs dérivent-elles anormalement ? | Taux de NULL, moyenne, valeurs aberrantes |
| Schéma | La structure a-t-elle changé ? | Colonne ajoutée ou supprimée, type modifié |
| Lineage | Quelle origine, quel impact ? | Graphe amont et aval d’une table |
| Qualité | Les règles métier sont-elles respectées ? | Unicité, valeurs acceptées, intégrité référentielle |
Une source en retard casse silencieusement tout ce qui en dépend ; une chute de volume est souvent le premier signal visible d’un problème en amont ; un changement de schéma casse des pipelines sans bruit.
Mesurer avec du SQL
Les requêtes ci-dessous ont été exécutées sur DuckDB avec des données d’exemple ; les fonctions de date (date_diff) varient légèrement d’un moteur à l’autre.
Fraîcheur
create table commandes_chargees(commande_id int, montant decimal(10,2), charge_le timestamp);
insert into commandes_chargees values
(1,10,'2026-10-20 06:00'),(2,20,'2026-10-21 06:00'),(3,30,'2026-10-22 06:00'),
(4,40,'2026-10-23 06:00'),(5,50,'2026-10-24 06:00');
select max(charge_le) as dernier_chargement,
date_diff('hour', max(charge_le), timestamp '2026-10-24 12:00') as heures_de_retard
from commandes_chargees;Avec un dernier chargement à 6 h et un contrôle à 12 h, le retard est de 6 heures (date_diff('hour', …) compte les frontières d’heure franchies, non les heures pleines écoulées). Un seuil d’alerte à 12 h et un seuil d’erreur à 24 h donnent le statut OK.
Volume
On compare le volume du jour à la moyenne et à l’écart type des jours précédents.
create table volumes(jour date, n int);
insert into volumes values
('2026-10-17',1000),('2026-10-18',1020),('2026-10-19',980),('2026-10-20',1010),
('2026-10-21',990),('2026-10-22',1005),('2026-10-23',1000),('2026-10-24',450);
with ref as (
select avg(n) as moy, stddev_samp(n) as ecart
from volumes where jour < date '2026-10-24'
)
select v.n, round(ref.moy, 1) as moyenne,
round((v.n - ref.moy) / ref.ecart, 1) as ecart_en_sigmas
from volumes v, ref
where v.jour = date '2026-10-24';Avec ces sept jours proches de 1 000 lignes (moyenne 1 000,7), le jour à 450 lignes obtient un écart de -42,2 écarts types, valeur négative puisque le volume est sous la moyenne : anomalie. La valeur dépend de la dispersion des jours de référence, d’où l’intérêt de fournir les données. Un seuil à 3 écarts types est un point de départ, pas une règle : à ajuster sur l’historique réel pour éviter le bruit.
Qualité
create table commandes_chk(commande_id int, client_id int, montant decimal(10,2));
insert into commandes_chk values (1,1,10),(1,1,10),(2,NULL,20),(3,9,-5);
create table clients_chk(client_id int);
insert into clients_chk values (1),(2);
-- unicité : commandes dupliquées
select commande_id, count(*) from commandes_chk group by 1 having count(*) > 1;
-- intégrité référentielle : commandes sans client connu
select o.* from commandes_chk o
left join clients_chk c using (client_id)
where o.client_id is not null and c.client_id is null;
-- valeurs invalides
select * from commandes_chk where montant < 0;Un test dbt est exactement cela : une requête qui renvoie les lignes en échec, et le test réussit si elle n’en renvoie aucune.2
Avec dbt
Fraîcheur des sources
On déclare, par source, une colonne de date de chargement et des seuils :
sources:
- name: shop
config:
freshness:
warn_after: {count: 12, period: hour}
error_after: {count: 24, period: hour}
loaded_at_field: _etl_loaded_at
tables:
- name: orders
- name: product_skus
config:
freshness: null # pas de contrôle pour cette tableLa documentation précise que dbt build n’inclut pas les contrôles de fraîcheur : ils se lancent séparément (dbt source freshness, ou dbt freshness à partir de dbt v2, où l’ancienne commande est considérée comme héritée), en général comme première étape d’un job.3 Selon la version de dbt, la configuration freshness se place dans un bloc config ou au niveau de la source : vérifier la syntaxe de la version utilisée.
Tests et sévérité
Les tests génériques fournis (unique, not_null, accepted_values, relationships) couvrent l’angle qualité. Un test peut échouer en warn ou en error, et les lignes en échec peuvent être conservées avec store_failures pour les examiner.2
Un journal d’exécution
Chaque invocation de dbt produit un fichier run_results.json qui décrit, pour chaque nœud exécuté, son identifiant, son statut, son temps d’exécution et des métadonnées renvoyées par la base, dont le nombre de lignes affectées quand l’adaptateur le fournit (la valeur peut valoir -1 si elle ne s’applique pas, par exemple pour la création d’une vue).4 En agrégeant ces fichiers, on obtient des temps moyens par modèle, des taux d’échec de tests, et un signal de volume.
Pour les conserver dans une table, un hook on-run-end appelle une macro qui reçoit la variable results :5
# dbt_project.yml
on-run-end:
- "{{ log_dbt_results(results) }}"Le code complet de ce journal (modèle de la table, hook et macros) est dans Journal d’exécution dbt, macros complètes : la macro insère elle-même les lignes avec run_query et le fait par colonnes nommées, jamais par position, car l’ajout automatique de colonnes (on_schema_change: append_new_columns) place les nouvelles colonnes à la fin de la table physique et décalerait un values (...) positionnel.
Ce journal couvre les angles volume (via les lignes affectées) et lineage d’exécution (si l’on y ajoute les dépendances observées). Il ne couvre pas la fraîcheur des sources, la distribution, le schéma ni la qualité : celles-ci demandent les contrôles de fraîcheur, des tests dédiés et un outil de lineage.
Points de conception d'un journal partagé
- Si plusieurs projets écrivent dans la même table, ajouter une colonne de provenance (nom du projet) à la clé analytique.
- Prévoir une rétention (suppression des lignes anciennes en fin de run, avec un seuil paramétrable) : un journal qui grossit sans limite devient un coût.
- Le SQL d’insertion varie selon le moteur (littéraux, échappement) : à adapter, par exemple avec une macro commune.
Lineage et schéma
Le lineage relie les données, les modèles et leurs dépendances pour savoir d’où vient un problème et jusqu’où il se propage. OpenLineage est un standard ouvert de collecte de métadonnées de lineage : il définit un modèle générique de dataset, de job et de run, extensible par des facets (des métadonnées ajoutées à une entité), pour que les outils échangent le lineage sans intégration sur mesure.6 Les changements de schéma à l’exécution relèvent plutôt de ce type d’outil que d’un simple journal de run dbt.
Du signal à l’engagement : SLI, SLO, SLA
Un indicateur ne devient un engagement qu’avec un objectif et une conséquence. Le livre de Google sur la fiabilité des services les distingue :7
| Terme | Sens | Exemple de données |
|---|---|---|
| SLI (indicateur) | Une mesure quantitative précise d’un aspect du service | Délai depuis le dernier chargement |
| SLO (objectif) | Une valeur cible ou une plage pour un SLI | Fraîcheur inférieure à 4 heures |
| SLA (accord) | Un contrat avec ses utilisateurs, avec des conséquences en cas de non-respect | Pénalité si la donnée arrive après 8 h |
Pour savoir si l’on parle d’un SLO ou d’un SLA, la question est : que se passe-t-il si l’objectif n’est pas tenu ? Sans conséquence explicite, c’est un SLO. Un contrat de données exprime ces engagements dans sa section de niveaux de service : Data contract et ODCS. Les pipelines de données se jugent surtout sur leur débit et sur le délai de bout en bout, de l’ingestion à la fin du traitement.7
Mettre en place, progressivement
Ordre conseillé
- Comprendre ce qui existe : lire les temps d’exécution et les échecs par modèle, d’abord sur un seul projet.
- Réduire le temps de diagnostic : stabiliser quelques requêtes de diagnostic dans un document d’exploitation (runbook).
- Prioriser la fiabilisation : choisir les modèles critiques et les tests qui comptent.
- Alerter sans bruit : fixer les seuils sur l’historique réel et cibler les canaux.
Commencer par alerter est la voie la plus rapide vers des alertes ignorées.
Notes liées
- Data contract et ODCS : l’engagement écrit (qualité, SLA) que l’observabilité vérifie.
- Data product : le produit dont on mesure la fiabilité.
- dbt — Fondamentaux : sources, tests, hooks.
- Idempotence et backfill : relances et rejeu après un incident.
- Journal d’exécution dbt, macros complètes : le code complet du journal.
- dbt_bigquery_monitoring : l’observabilité du coût côté BigQuery.
- Glossaire data engineering : lineage, SLA et SLO.
- MOC Data Engineering : carte du vault.
Notes de bas de page
-
Monte Carlo, What is data observability, consulté le 24 octobre 2026 (cinq piliers : fraîcheur, qualité, volume, schéma, lineage), et DataTalks.Club, data quality, observability, reliability (variante avec la distribution). La définition de data downtime donnée ici est une reformulation. ↩
-
dbt, Data tests, consulté le 24 octobre 2026. ↩ ↩2
-
dbt, Sources, Source freshness et commande freshness, consultés le 24 octobre 2026. ↩
-
dbt, Run results JSON, consulté le 24 octobre 2026. ↩
-
dbt, on-run-start et on-run-end, consulté le 24 octobre 2026. ↩
-
OpenLineage, documentation, consultée le 24 octobre 2026 (version 1.54.0). ↩
-
Google, Site Reliability Engineering, chapitre 4 : Service Level Objectives, consulté le 24 octobre 2026. ↩ ↩2