Lecția 60 s-a încheiat pe o conexiune: SLO-urile fără testare a calității sunt presupuneri. SLI-ul „mai puțin de 0,1% din rândurile așteptate lipsesc” trebuie calculat undeva, iar acel undeva este un framework de calitate a datelor care rulează continuu, evaluează aserțiuni asupra datelor și raportează pass sau fail într-un mod pe care mecanica SLO îl poate consuma. Lecția asta e despre framework-uri.
Piața s-a coagulat în jurul a trei unelte open-source: dbt tests, Great Expectations și Soda. Se suprapun ca posibilități, dar au centre de greutate distincte, iar răspunsul corect în majoritatea platformelor de date este să folosești mai mult decât una. Să le alegi între ele ca pe niște competitori produce de obicei un rezultat mai prost decât să înțelegi ce face fiecare cel mai bine.
Cadrul de referință al întregii lecții: testarea calității datelor răspunde la o singură întrebare. Sunt datele pe care urmează să le folosesc demne de încredere? „Urmează să le folosesc” contează. Un test care rulează o dată pe săptămână prinde problemele cu o săptămână întârziere. Framework-urile de mai jos sunt proiectate să ruleze la fiecare execuție de pipeline, ceea ce le transformă în controale de calitate efective, nu în audituri periodice.
Cele patru dimensiuni clasice
Calitatea datelor a acumulat taxonomii de-a lungul anilor. Versiunea cu patru dimensiuni e cea pe care majoritatea echipelor o adoptă pentru că acoperă modurile practice de eșec fără să prolifereze în categorii pe care nu le mai ține nimeni minte.
Schema întreabă dacă coloanele există cu tipurile corecte. Un pipeline așteaptă customer_id ca integer non-null; sursa îl furnizează ca string cu zerouri în față care pot sau nu să fie prezente. Testele de schema prind drift-ul de tipuri, coloanele redenumite și coloanele șterse. Schema este dimensiunea cea mai ieftin de testat și cea mai valoroasă: majoritatea incidentelor reale de date pornesc de la o schimbare de schema nedetectată.
Completeness întreabă dacă valorile cerute sunt prezente. O coloană declarată NOT NULL în contract dar de fapt 5% null în date este un eșec de completeness. Testele de completeness pot fi la nivel de coloană (e coloana asta non-null acolo unde ar trebui?) sau la nivel de rând (sunt prezente toate rândurile așteptate, comparând count-ul cu o cardinalitate așteptată sau cu rularea anterioară plus o toleranță).
Validity întreabă dacă valorile se încadrează în intervale sau seturi așteptate. Sumele comenzilor ar trebui să fie pozitive. Codurile de țară ar trebui să aparțină listei ISO. Adresele de email ar trebui să corespundă unui pattern. Testele de validity prind datele care sunt tehnic prezente, dar semantic greșite.
Consistency întreabă dacă tabelele înrudite sunt de acord. Cheile străine există în tabela părinte. Suma totalurilor pe linii corespunde header-ului comenzii. Totalul venitului zilnic se potrivește între warehouse și ERP-ul sursă. Consistency prinde eșecuri pe care celelalte trei le ratează, pentru că violările de consistency pot apărea chiar și când fiecare tabelă individuală arată sănătoasă.
Tiparul în practică e să te gândești la dimensiuni ca la o ierarhie de triaj. Schema se rupe înaintea completeness, completeness înaintea validity, validity înaintea consistency. Testează în ordinea asta; dacă schema e greșită, celelalte teste vor fi gălăgios greșite din motive greșite.
dbt tests: în warehouse, declarative, gratis
Framework-ul de teste dbt (https://docs.getdbt.com/docs/build/data-tests, consultat 2026-05-01) este opțiunea pe care majoritatea echipelor de date rezidente în warehouse deja o au. Testele sunt declarate în YAML alături de definițiile modelelor și se execută ca SQL pe warehouse. Testele incluse acoperă cazurile de mare frecvență.
not_null aserează că o coloană nu are null-uri. unique aserează că nu există duplicate. accepted_values aserează că valorile se încadrează într-un set enumerat. relationships aserează integritatea cheilor străine verificând că fiecare valoare din coloana A există în coloana B a altei tabele. Dincolo de astea, dbt_utils.expression_is_true rulează o expresie SQL booleană arbitrară ca test, ceea ce acoperă coada lungă.
Punctele tari ale testelor dbt sunt pragmatice. Testele trăiesc în version control lângă modelele pe care le protejează, deci evoluează împreună. Rulează ca parte din invocarea dbt, integrate în același pipeline CI pe care l-a acoperit lecția 51. Produc SQL care rulează pe warehouse, deci nu există mișcare de date și nici un environment de compute separat.
Slăbiciunile sunt corolarul. Testele dbt rulează numai în interiorul warehouse-ului. Nu pot testa fișiere în object storage înainte să fie încărcate, nu pot testa date care părăsesc warehouse-ul către consumatori downstream, nu pot testa ușor în mod continuu între rulările programate. Tipurile de teste dincolo de cele patru incluse necesită SQL custom sau pachete precum dbt_expectations.
Pentru o platformă centrată pe warehouse unde dbt este stratul de transformare, testele dbt acoperă 70-80% din testarea practică de calitate; restul de 20-30% justifică una dintre celelalte unelte.
Great Expectations: portabil, conștient de fișiere, profilare
Great Expectations (https://docs.greatexpectations.io/, consultat 2026-05-01), prescurtat GX, este o bibliotecă Python aflată în producție la scară din jurul anului 2019. Filozofia lui este că testele de date ar trebui să fie aserțiuni portabile despre forma datelor, exprimabile într-un format de tip JSON care călătorește cu datele în loc să fie legat de un motor de bază de date specific.
Unitatea de testare este „expectation”. expect_column_values_to_not_be_null, expect_column_values_to_be_in_set, expect_column_mean_to_be_between. Biblioteca este mare, cu zeci de expectations care acoperă proprietăți numerice, categoriale și statistice, plus posibilitatea de a defini expectations custom în Python.
Punctul tare este aria de acoperire. GX rulează pe DataFrame-uri pandas, DataFrame-uri Spark, warehouse-uri SQL (prin SQLAlchemy) și fișiere în object storage. Aceeași aserțiune poate fi aplicată în mai multe etape: pe un fișier Parquet care aterizează în S3, pe un DataFrame Spark în mijlocul transformării, pe o tabelă din warehouse după ce dbt a materializat-o.
GX face și profilare de date. Dacă îl îndrepți spre un dataset nou, produce un set generat automat de expectations candidate bazate pe distribuția observată. Profilările nu sunt production-ready așa cum sunt (prea strânse, fiecare fluctuație aleatoare le va face să eșueze), dar sunt un punct de plecare excelent.
Slăbiciunea este complexitatea operațională. GX are un model conceptual mai bogat decât dbt sau Soda: data sources, batches, expectation suites, validation results, data docs. Pentru echipele care au warehouse-uri și dbt și vor doar ca testele eșuate să facă pipeline-ul să eșueze, GX deseori pare exagerat. Acolo unde strălucește este la granițe: ingestie, egress și validare a fișierelor înainte ca vreun compute să le atingă. dbt e mai bun în interiorul warehouse-ului; GX e mai bun la margini.
Soda Core și Soda Cloud: reguli YAML, produs focalizat
Soda (https://docs.soda.io/, consultat 2026-05-01) se împarte în două părți: Soda Core, biblioteca open-source care rulează check-uri, și Soda Cloud, stratul comercial de dashboard și alerting. Diferențiatorul este limbajul de reguli: SodaCL, un limbaj bazat pe YAML proiectat să se citească ca aserțiuni în engleză despre date.
Un check SodaCL arată ca missing_count(email) = 0 sau duplicate_count(customer_id) = 0 sau freshness(updated_at) < 1d. Vocabularul este mai strâns decât GX (mai puține tipuri de check-uri) și sintaxa este proiectată să fie lizibilă de analiști și data product manageri, nu doar de ingineri. Pentru organizații în care calitatea datelor este deținută de o echipă cu skill-uri amestecate, abordarea YAML-și-engleză reduce frecarea de a ține check-urile actualizate.
Soda rulează pe warehouse-uri prin conectori și pe date streaming prin integrare cu Spark și Kafka. Se poziționează între dbt și GX: mai flexibil decât dbt (pentru că nu este legat de ciclul de viață al dbt), mai focalizat decât GX (pentru că face mai puține lucruri în mod deliberat). Produsul Cloud adaugă dashboard-uri, alerting, view-uri de lineage și fluxuri de incident pentru echipele care vor un UI gestionat în loc să-l construiască peste biblioteca open-source.
În practică, alegerea Soda se reduce deseori la dacă organizația apreciază limbajul de reguli lizibil și dashboard-urile Cloud suficient cât să standardizeze pe ele, sau preferă bogăția GX sau simplitatea dbt.
Tipare care funcționează
Trei tipare operaționale rezistă peste unelte.
Testează la granițe, nu la fiecare pas. Pipeline-urile de date au multe etape: ingestie, staging, transformare, marts, exporturi. Punerea de teste la fiecare etapă produce o mare de aserțiuni pe care nu le citește nimeni și care eșuează corelat când ceva upstream se rupe. Tiparul care funcționează este să testezi la ingestie (datele brute sunt formate corect, schema corespunde contractului, count-ul de rânduri e plauzibil) și să testezi la output (datele publicate sunt corecte, marts-urile au cardinalitățile corecte, metricele se reconciliază cu sursa). Pașii intermediari primesc un strat subțire de teste structurale (schemas) dar nu suita completă de calitate. Dacă ingestia e corectă și output-ul e corect, mijlocul e acoperit de testele de model în dbt sau echivalentul lui.
Pune testele pe niveluri de severitate. Nu fiecare eșec de test este un eveniment care blochează pipeline-ul. Un nivelaj util: testele critice blochează pipeline-ul (rularea se oprește și datele proaste nu se propagă), testele de avertizare alertează dar permit continuarea (datele curg dar se trimite o notificare), testele informaționale doar loghează (vizibile în dashboard-uri dar fără notificare). Nivelajul ține volumul de alerting gestionabil și previne capcana „totul e critic” care distruge sănătatea on-call. Un non-null la nivel de coloană pe o cheie primară este critic. Un drift de count de rânduri mai mare de 20% este avertizare. Un drift de count mai mare de 5% este informațional.
Rulează continuu, nu doar la rulările de pipeline. Pipeline-ul rulează o dată pe oră sau o dată pe zi; problemele de calitate a datelor pot apărea între rulări. Rularea unui subset al check-urilor celor mai importante pe un program continuu (la fiecare cinci minute, la fiecare cincisprezece) prinde drift-urile mai repede și hrănește bucla de măsurare SLO din lecția 60. Toate uneltele suportă asta, deși detaliile variază: dbt poate fi programat separat pentru teste, Soda are un scheduler cloud, GX rulează oriunde îl rulează orchestratorul.
Capcana supra-testării
Modul de eșec care distruge programele de calitate a datelor este supra-testarea. Tiparul e recognoscibil: un framework de calitate este adoptat, cineva rulează profiler-ul GX sau echivalentul lui, sute de teste generate sunt commit-uite, și în trei luni echipa are sute de teste la care nu se uită nimeni. Unele eșuează la fiecare rulare pentru că aserțiunile sunt prea stricte. Unele trec în timp ce datele sunt cu adevărat greșite pentru că aserțiunea avea forma greșită. Echipa începe să ignore rezultatele testelor ca pe o categorie, ceea ce înseamnă că framework-ul nu mai oferă semnal, ceea ce înseamnă că programul de calitate a eșuat efectiv chiar dacă testele încă rulează.
Trei anti-tipare specifice produc rezultatul ăsta.
Teste fără proprietar. Testul a fost generat de o unealtă sau scris de un inginer care între timp a schimbat echipa. Când eșuează, nimeni nu știe dacă eșecul e real sau dacă aserțiunea era greșită de la început. Reacția implicită e să dezactivezi testul, și încet suita de teste se golește.
Teste cu toleranța greșită. Count-ul de rânduri așteptat este „între 1000 și 1010” pentru că ăla a fost intervalul observat într-o săptămână liniștită. Prima săptămână aglomerată, count-ul ajunge la 1500, iar testul eșuează nu pentru că ceva e greșit, ci pentru că toleranța n-a fost niciodată actualizată. Fiecare test cu un interval numeric trebuie revizitat pe măsură ce datele scalează.
Teste care trec în timp ce datele sunt greșite. Testul de accepted-values permite ['active', 'inactive', 'unknown']; datele încep să emită 'pending' din cauza unei stări noi în sistemul sursă, și testul îl prinde. Până aici bine. Dar un test care a aserat „fără null-uri în coloana email” trece fericit în timp ce coloana email e în mare parte string-uri goale, pentru că datele erau greșite dar aserțiunea n-a testat pentru problema reală. Alegerea formei corecte a aserțiunii este mai importantă decât a avea multe aserțiuni.
Disciplina care previne capcana este legarea testelor de SLO-uri. Lecția 60 a spus că SLO-ul este „mai puțin de 0,1% din rândurile așteptate lipsesc pe zi”. Testele corespunzătoare sunt exact cele care, dacă eșuează, indică că SLO-ul e în pericol. Fiecare test ar trebui să poată răspunde la întrebarea „ce SLO protejează ăsta și ce s-ar întâmpla dacă l-aș elimina?”. Testele care nu pot răspunde la nici una dintre întrebări sunt candidate la ștergere.
Verificări de calitate la granițe
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]
Diagrama arată stratificarea recomandată. Verificarea de ingestie (Great Expectations pe fișier, sau schema-on-read în loader) verifică dacă datele brute sunt formate corect înainte să intre în platformă. Verificarea de schema pe zona de aterizare brută prinde drift-ul de tipuri înainte ca vreun compute să atingă datele. Verificarea structurală mid-pipeline (not_null și unique din dbt pe modelele staging) prinde ieftin eșecurile structurale. Verificarea de reconciliere a output-ului (Soda sau SQL custom care compară totalurile din warehouse cu totalurile din sistemul sursă) prinde eșecurile de acuratețe la granița unde altfel ar scurge la consumatori. Fiecare strat are un scop focalizat; împreună acoperă cele patru dimensiuni fără duplicare.
Lecția se conectează în sus: fiecare verificare din diagramă produce un SLI, fiecare SLI hrănește un SLO, fiecare SLO are un nivel, iar SLO-urile de nivel 1 sunt cele pentru care on-call-ul e paged. Lecția 62 prinde firul: când o verificare eșuează, când un SLO de prospețime arde din buget, când o reconciliere de acuratețe semnalează o varianță de cinci cifre, ce face efectiv echipa? Detecția e jumătate din muncă. Cealaltă jumătate e disciplina de incident response care transformă o alertă într-o recuperare.