Architettura di dati e sistemi, dalle fondamenta Lezione 61 / 80

Data quality: Great Expectations, Soda, dbt tests

Test dichiarativi sui dati. I tre tool, i pattern che funzionano, e la trappola dell'over-testing.

La lezione 60 si è chiusa su una connessione: gli SLO senza testing della qualità sono ipotesi. Lo SLI “meno dello 0,1% delle righe attese mancanti” deve essere calcolato da qualche parte, e quel qualche parte è un framework di data quality che gira in continuo, valuta asserzioni sui dati, e riporta pass o fail in un formato che la macchina degli SLO può consumare. Questa lezione parla di quei framework.

Il mercato è converso su tre tool open source: dbt tests, Great Expectations, e Soda. Si sovrappongono nelle capability ma hanno baricentri distinti, e la risposta giusta nella maggior parte delle data platform è usarne più di uno. Sceglierli come concorrenti di solito produce un risultato peggiore rispetto a capire in cosa ciascuno è migliore.

L’inquadramento di tutta la lezione: il testing della data quality risponde a una sola domanda. I dati che sto per usare sono affidabili? Quel “che sto per usare” conta. Un test che gira una volta a settimana intercetta i problemi con una settimana di ritardo. I framework qui sotto sono progettati per girare su ogni esecuzione della pipeline, ed è questo che li rende veri controlli di qualità invece di audit periodici.

Le quattro dimensioni classiche

La data quality ha accumulato tassonomie nel corso degli anni. La versione a quattro dimensioni è quella su cui la maggior parte dei team si stabilizza, perché copre i failure mode pratici senza proliferare in categorie che nessuno riesce a tenere in testa.

Schema chiede se le colonne esistono con i tipi giusti. Una pipeline si aspetta customer_id come intero non nullo; la sorgente lo fornisce come stringa con zeri iniziali che a volte ci sono e a volte no. I test di schema intercettano il drift di tipi, le colonne rinominate, e le colonne rimosse. Lo schema è la dimensione più economica da testare e quella di maggior valore: la maggior parte degli incidenti dati in produzione comincia con un cambio di schema non rilevato.

Completeness chiede se i valori richiesti sono presenti. Una colonna dichiarata NOT NULL nel contratto ma in realtà al 5% nulla nei dati è un fallimento di completeness. I test di completeness possono essere a livello di colonna (questa colonna è non nulla dove dovrebbe esserlo?) o a livello di riga (sono presenti tutte le righe attese, confrontando i conteggi rispetto a una cardinalità attesa o rispetto al run precedente più una tolleranza).

Validity chiede se i valori cadono in range o set attesi. Gli importi degli ordini dovrebbero essere positivi. I codici paese dovrebbero appartenere alla lista ISO. Gli indirizzi email dovrebbero corrispondere a un pattern. I test di validity intercettano dati tecnicamente presenti ma semanticamente sbagliati.

Consistency chiede se le tabelle correlate concordano tra loro. Le foreign key esistono nella tabella padre. La somma dei totali delle line item corrisponde all’header dell’ordine. Il totale del fatturato giornaliero corrisponde tra warehouse e ERP sorgente. La consistency intercetta i fallimenti che le altre tre mancano, perché le violazioni di consistency possono verificarsi anche quando ogni singola tabella sembra in salute.

Il pattern in pratica è pensare alle dimensioni come a una gerarchia di triage. Lo schema si rompe prima della completeness, la completeness prima della validity, la validity prima della consistency. Testa in quell’ordine; se lo schema è sbagliato, gli altri test saranno rumorosamente sbagliati per la ragione sbagliata.

dbt tests: dentro il warehouse, dichiarativi, gratis

Il framework di test di dbt (https://docs.getdbt.com/docs/build/data-tests, consultato 2026-05-01) è l’opzione che la maggior parte dei team data residenti nel warehouse ha già. I test sono dichiarati in YAML accanto alle definizioni dei modelli ed eseguiti come SQL contro il warehouse. I test inclusi coprono i casi ad alta frequenza.

not_null asserisce che una colonna non ha null. unique asserisce che non ci sono duplicati. accepted_values asserisce che i valori cadono in un set enumerato. relationships asserisce l’integrità delle foreign key controllando che ogni valore nella colonna A esista nella colonna B di un’altra tabella. Oltre a questi, dbt_utils.expression_is_true esegue un’espressione booleana SQL arbitraria come test, il che copre la coda lunga.

I punti di forza dei dbt tests sono pragmatici. I test vivono in version control accanto ai modelli che proteggono, quindi evolvono insieme. Girano come parte dell’invocazione di dbt, integrati nella stessa pipeline di CI coperta dalla lezione 51. Producono SQL che gira sul warehouse, quindi non c’è movimento di dati né un ambiente di compute separato.

I punti deboli sono il corollario. I dbt tests girano solo dentro il warehouse. Non possono testare file in object storage prima che vengano caricati, non possono testare dati che lasciano il warehouse verso consumer downstream, non possono facilmente testare in continuo tra run schedulati. I tipi di test oltre i quattro inclusi richiedono SQL custom o package come dbt_expectations.

Per una piattaforma warehouse-centric dove dbt è il transformation layer, i dbt tests coprono il 70-80% del testing pratico di qualità; il restante 20-30% giustifica uno degli altri tool.

Great Expectations: portatile, file-aware, profilante

Great Expectations (https://docs.greatexpectations.io/, consultato 2026-05-01), abbreviato GX, è una libreria Python in produzione su scala dal 2019 circa. La sua filosofia è che i test sui dati dovrebbero essere asserzioni portatili sulla forma dei dati, esprimibili in un formato JSON-like che viaggia con i dati invece di essere legato a uno specifico engine di database.

L’unità di testing è la “expectation”. expect_column_values_to_not_be_null, expect_column_values_to_be_in_set, expect_column_mean_to_be_between. La libreria è grande, con decine di expectation che coprono proprietà numeriche, categoriche e statistiche, più la possibilità di definire expectation custom in Python.

Il punto di forza è la portata. GX gira su pandas DataFrame, Spark DataFrame, warehouse SQL (via SQLAlchemy), e file in object storage. La stessa asserzione può essere applicata a più stadi: su un file Parquet che atterra in S3, su uno Spark DataFrame a metà trasformazione, su una tabella di warehouse dopo che dbt l’ha materializzata.

GX fa anche profiling dei dati. Puntandolo su un nuovo dataset produce un set generato automaticamente di expectation candidate basate sulla distribuzione osservata. I profili non sono pronti per la produzione così come sono (troppo stretti, ogni fluttuazione casuale li farà fallire) ma sono un eccellente punto di partenza.

Il punto debole è la complessità operativa. GX ha un modello concettuale più ricco di dbt o Soda: data source, batch, suite di expectation, validation result, data docs. Per team che hanno warehouse e dbt e vogliono solo che i test falliti facciano fallire la pipeline, GX spesso sembra eccessivo. Dove brilla è ai confini: ingestion, egress, e validazione di file prima che qualunque compute li tocchi. dbt è migliore dentro il warehouse; GX è migliore ai bordi.

Soda Core e Soda Cloud: regole YAML, prodotto focalizzato

Soda (https://docs.soda.io/, consultato 2026-05-01) si divide in due parti: Soda Core, la libreria open source che esegue i check, e Soda Cloud, il layer commerciale di dashboard e alerting. Il differenziatore è il linguaggio delle regole: SodaCL, un linguaggio basato su YAML progettato per leggersi come asserzioni in inglese sui dati.

Un check SodaCL ha la forma missing_count(email) = 0 o duplicate_count(customer_id) = 0 o freshness(updated_at) < 1d. Il vocabolario è più stretto di GX (meno tipi di check) e la sintassi è progettata per essere leggibile da analyst e data product manager, non solo da ingegneri. Per organizzazioni dove la data quality è di proprietà di un team con skill miste, l’approccio YAML-e-inglese riduce l’attrito nel tenere i check aggiornati.

Soda gira contro warehouse via connector e contro dati in streaming via integrazione con Spark e Kafka. Si posiziona tra dbt e GX: più flessibile di dbt (perché non è legato al ciclo di vita di dbt), più focalizzato di GX (perché fa meno cose deliberatamente). Il prodotto Cloud aggiunge dashboard, alerting, viste di lineage, e workflow di incident per team che vogliono una UI gestita invece di costruirne una sopra la libreria open source.

In pratica, la scelta di Soda spesso si riduce a se l’organizzazione valuta il linguaggio leggibile delle regole e le dashboard Cloud abbastanza da standardizzarsi su esso, oppure preferisce la ricchezza di GX o la semplicità di dbt.

Pattern che funzionano

Tre pattern operativi valgono trasversalmente sui tool.

Testa ai confini, non a ogni step. Le data pipeline hanno molti stadi: ingestion, staging, transformation, mart, export. Mettere test a ogni stadio produce un mare di asserzioni che nessuno legge e che falliscono in modo correlato quando qualcosa upstream si rompe. Il pattern che funziona è testare in ingestion (i dati raw hanno la forma giusta, lo schema corrisponde al contratto, il conteggio righe è plausibile) e testare in output (i dati pubblicati sono corretti, i mart hanno le cardinalità giuste, le metriche riconciliano contro la sorgente). Gli step intermedi ricevono un sottile strato di test strutturali (schemi) ma non l’intera suite di qualità. Se l’ingestion è giusta e l’output è giusto, il mezzo è coperto dai test dei modelli in dbt o equivalenti.

Stratifica i test per severità. Non ogni fallimento di test è un evento bloccante per la pipeline. Una stratificazione utile: i test critical bloccano la pipeline (il run si ferma e i dati cattivi non si propagano), i test warning allertano ma permettono di proseguire (i dati scorrono ma viene mandata una notifica), i test informational solo loggano (visibili in dashboard ma senza notifica). La stratificazione mantiene il volume di alerting gestibile e previene la trappola del “tutto è critico” che distrugge la sanità mentale dell’on-call. Un non-null a livello di colonna su una primary key è critical. Un drift del conteggio righe oltre il 20% è warning. Un drift del conteggio righe oltre il 5% è informational.

Esegui in continuo, non solo sui run della pipeline. La pipeline gira una volta all’ora o una volta al giorno; i problemi di data quality possono insorgere tra un run e l’altro. Eseguire un sottoinsieme dei check più importanti su uno schedule continuo (ogni cinque minuti, ogni quindici) intercetta i drift più rapidamente e alimenta il loop di misurazione degli SLO della lezione 60. I tool lo supportano tutti, anche se i dettagli variano: dbt può essere schedulato separatamente per i test, Soda ha uno scheduler cloud, GX gira ovunque l’orchestratore lo faccia girare.

La trappola dell’over-testing

Il failure mode che distrugge i programmi di data quality è l’over-testing. Il pattern è riconoscibile: viene adottato un framework di qualità, qualcuno fa girare il profiler di GX o equivalente, centinaia di test generati vengono committati, e nel giro di tre mesi il team ha centinaia di test che nessuno guarda. Alcuni falliscono ad ogni run perché le asserzioni sono troppo strette. Alcuni passano mentre i dati sono genuinamente sbagliati perché l’asserzione era della forma sbagliata. Il team comincia a ignorare i risultati dei test come categoria, il che significa che il framework non fornisce più segnale, il che significa che il programma di qualità è di fatto fallito anche se i test continuano a girare.

Tre anti-pattern specifici producono questo esito.

Test senza owner. Il test è stato generato da un tool o scritto da un ingegnere che da allora ha cambiato team. Quando fallisce, nessuno sa se il fallimento è reale o se l’asserzione era sbagliata fin dall’inizio. La reazione di default è disabilitare il test, e lentamente la suite di test si svuota.

Test con la tolleranza sbagliata. Il conteggio righe atteso è “tra 1000 e 1010” perché quello era il range osservato durante una settimana tranquilla. Alla prima settimana di traffico intenso il conteggio sale a 1500, e il test fallisce non perché qualcosa sia sbagliato ma perché la tolleranza non è mai stata aggiornata. Ogni test con un range numerico va rivisitato man mano che i dati scalano.

Test che passano mentre i dati sono sbagliati. Il test sui valori accettati permette ['active', 'inactive', 'unknown']; i dati cominciano a emettere 'pending' per via di un nuovo stato nel sistema sorgente, e il test lo intercetta. Fin qui tutto bene. Ma un test che asseriva “nessun null nella colonna email” passa felicemente mentre la colonna email è per lo più stringhe vuote, perché i dati erano sbagliati ma l’asserzione non testava il problema effettivo. Scegliere la forma giusta dell’asserzione è più importante che avere molte asserzioni.

La disciplina che previene la trappola è legare i test agli SLO. La lezione 60 ha detto che lo SLO è “meno dello 0,1% delle righe attese mancanti per giorno”. I test corrispondenti sono esattamente quelli che, se falliscono, indicano che lo SLO è a rischio. Ogni test dovrebbe poter rispondere alla domanda “quale SLO protegge questo, e cosa succederebbe se lo rimuovessi?”. I test che non rispondono a nessuna delle due domande sono candidati alla cancellazione.

Quality check ai confini

flowchart LR
    A[Source System] --> B[Ingestion check]
    B --> C[Raw landing zone]
    C --> D[Schema check]
    D --> E[Transform pipeline]
    E --> F[Mid-pipeline structural check]
    F --> G[Warehouse marts]
    G --> H[Output reconciliation check]
    H --> I[Published data]
    I --> J[Downstream consumers]

Il diagramma mostra la stratificazione raccomandata. L’ingestion check (Great Expectations sul file, o schema-on-read nel loader) verifica che i dati raw abbiano la forma giusta prima di entrare nella piattaforma. Lo schema check sulla raw landing zone intercetta il drift di tipi prima che qualunque compute tocchi i dati. Il check strutturale a metà pipeline (not_null e unique di dbt sui modelli di staging) intercetta i fallimenti strutturali a basso costo. Il check di riconciliazione in output (Soda o SQL custom che confronta i totali del warehouse contro i totali del sistema sorgente) intercetta i fallimenti di accuratezza al confine, dove altrimenti perderebbero verso i consumer. Ogni layer ha uno scopo focalizzato; insieme coprono le quattro dimensioni senza duplicazione.

La lezione si connette verso l’alto: ogni check nel diagramma produce uno SLI, ogni SLI alimenta uno SLO, ogni SLO ha un tier, e gli SLO di tier 1 sono quelli per cui l’on-call viene chiamato. La lezione 62 raccoglie quel filo: quando un check fallisce, quando uno SLO di freshness brucia budget, quando una riconciliazione di accuratezza segnala una varianza a cinque cifre, cosa fa davvero il team? Il rilevamento è metà del lavoro. L’altra metà è la disciplina di incident response che trasforma un alert in un recupero.

Cerca