Codi reproduïble i entorn reproduïble: dos dels tres artefactes del cicle de vida estan sota control. Falta el que canvia al seu propi ritme i sense avisar: les dades. El clients_churn.csv de CineClick continua sent un fitxer que el data engineer regenera periòdicament, trepitjant l'anterior — el problema núm. 3 del diagnòstic ("el dataset de la v3 ja no existeix"). En aquesta lliçó incorporem DVC (Data Version Control), l'eina que estén la disciplina de Git a les dades: veurem per què Git sol no serveix, quin és el model mental de DVC (petits metafitxers a Git, dades reals en una caché), i el flux complet aplicat al nostre dataset, inclòs el moment estrella: viatjar en el temps entre versions de les dades amb dues ordres.

Contingut

  1. Per què Git no serveix per a dades
  2. El model mental de DVC: metafitxers + caché + remote
  3. Mans a l'obra: dvc init i dvc add
  4. El remote: compartir dades amb dvc push i dvc pull
  5. Viatjar en el temps: una versió nova del dataset
  6. Què versiona cadascú: Git vs. DVC

Per què Git no serveix per a dades

La reacció instintiva seria git add data/raw/clients_churn.csv i llestos. A la lliçó anterior el vam enviar al .gitignore sense justificar-ho del tot; fem-ho ara. Git és una eina magnífica per a allò per a què va ser dissenyada —text, petit, amb diffs significatius— i els datasets incompleixen les tres condicions:

  • Mida: Git guarda la història completa del repositori, i cada clon se l'emporta sencera. El nostre CSV de joguina pesa uns pocs MB, però l'extracte real d'una plataforma d'streaming es mesura en GB, i amb un snapshot mensual el repositori s'engreixa sense fre: clonar el projecte acabaria descarregant anys de datasets morts. Els servidors Git (GitHub inclòs) a més imposen límits durs de mida de fitxer.
  • Binaris i diffs inútils: Git és eficient perquè emmagatzema diferències entre versions de text. Amb un CSV gran que es regenera sencer (o un Parquet, o imatges), cada versió és a la pràctica un blob nou complet, i un git diff de dos extractes de 2 milions de files no serveix de res a cap humà.
  • Ritmes diferents: com vam veure a la lliçó 01-02, codi i dades canvien a ritmes diferents i per causes diferents. Barrejar al mateix historial "refactoritzo features.py" amb "ha arribat l'extracte de juliol" embruta tots dos historials.

El que sí que volem conservar de Git és la seva semàntica: poder dir "el model de la release 1.2 es va entrenar amb aquestes dades exactes", tornar a qualsevol versió i compartir amb l'equip. La solució no és ficar les dades a Git, sinó ficar a Git una referència verificable a les dades.

El model mental de DVC: metafitxers + caché + remote

DVC (Data Version Control) és una eina de línia d'ordres, de codi obert, que treballa damunt de Git — no el substitueix. El seu truc és una separació neta:

  • A Git hi viuen uns metafitxers diminuts de text (extensió .dvc) que descriuen cada dada versionada: essencialment el seu hash MD5 (una empremta digital del contingut), la seva mida i la seva ruta. Ocupen bytes i es diffegen d'allò més bé.
  • Fora de Git, els fitxers reals viuen en una caché local (.dvc/cache/, organitzada per hash) i, per compartir-los, en un remote: qualsevol emmagatzematge — una carpeta de xarxa, S3, Google Cloud Storage, Azure Blob...
  • Al teu directori de treball, data/raw/clients_churn.csv és (segons el sistema) un enllaç o còpia materialitzada des de la caché: tu el fas servir amb normalitat, pandas ni se n'assabenta.
flowchart LR
    subgraph Git["Repositori Git (text, lleuger)"]
        M["clients_churn.csv.dvc<br/>md5: 3f2a..., size: 2.1 MB"]
    end
    subgraph Local["Màquina local"]
        W["data/raw/clients_churn.csv<br/>(fitxer de treball)"]
        C[".dvc/cache/<br/>(continguts per hash)"]
    end
    R["Remote<br/>(carpeta compartida / S3 / GCS)"]
    M -- "dvc checkout" --> W
    W -- "dvc add" --> C
    C -- "dvc push" --> R
    R -- "dvc pull" --> C

La conseqüència elegant: cada commit de Git fixa, via el hash del metafitxer, una versió exacta de les dades. Codi i dades queden lligats al mateix historial sense que les dades passin per Git. Canviar de versió de dades serà tan simple com canviar de commit i demanar a DVC que materialitzi el que aquell commit referencia.

Mans a l'obra: dvc init i dvc add

DVC és un paquet Python més, així que entra al projecte per la porta que vam muntar a 02-02: s'afegeix dvc>=3.50 a l'extra dev de pyproject.toml, es regenera el lock amb pip-compile i se sincronitza. Amb l'entorn activat, inicialitzem:

dvc init
git status
#   nous: .dvc/config  .dvc/.gitignore  .dvcignore
git commit -m "Inicialitza DVC al projecte"

dvc init crea el directori .dvc/ (configuració i, més endavant, la caché) i alguns fitxers auxiliars, tots pensats per ser commitejats. DVC sempre deixa engrunes de pa a Git: aquesta és la gràcia.

Ara posem el dataset sota control. Primer retirem data/ del .gitignore general que vam escriure a 02-01 (DVC gestionarà ignorats més fins pel seu compte) i executem:

dvc add data/raw/clients_churn.csv

Això fa tres coses: calcula el hash del fitxer, en guarda una còpia a la caché (.dvc/cache/), i crea el metafitxer data/raw/clients_churn.csv.dvc, a més d'afegir el CSV real a un .gitignore local perquè Git no el vegi. El metafitxer és així d'humil:

# data/raw/clients_churn.csv.dvc  (el genera DVC; no s'edita a mà)
outs:
- md5: 3f2a9c81d4be7a06c1e5f9a2b8d47e11
  size: 2184730
  hash: md5
  path: clients_churn.csv

Llegeix-lo com un resguard de consigna: "existeix un fitxer anomenat clients_churn.csv, de 2,1 MB, el contingut del qual té aquesta empremta MD5". Amb aquest hash, DVC pot localitzar el contingut exacte a qualsevol caché o remote. Canvia un sol byte del CSV i el hash canvia per complet — per això serveix com a identificador de versió. El que va a Git és el resguard:

git add data/raw/clients_churn.csv.dvc data/raw/.gitignore
git commit -m "Versiona el dataset de churn (extracte de juny) amb DVC"

Fixa't en el missatge de commit: descriure quina versió de les dades és aquesta ("extracte de juny") converteix el git log en l'historial de datasets que mai no vam tenir.

El remote: compartir dades amb dvc push i dvc pull

La caché és local: si Laura versiona el dataset a la seva màquina, Marc encara no el pot obtenir. El remote és a la dada el que GitHub al codi: el punt d'intercanvi. DVC suporta S3, GCS, Azure, SSH, carpetes locals o de xarxa... i es configura una vegada:

# Per al curs: un directori local que simula l'emmagatzematge compartit.
dvc remote add -d magatzem /srv/dvc-remote-cineclick
git add .dvc/config
git commit -m "Configura el remote de dades"

El flag -d el marca com a remote per defecte, i la configuració queda a .dvc/config — text, commitejat, compartit. A la vida real de CineClick això apuntaria a un bucket (n'hi hauria prou amb dvc remote add -d magatzem s3://cineclick-mlops-dades més les credencials, que mai van a Git — DVC les guarda a part amb dvc remote modify --local). Per seguir el curs, la carpeta local simula el bucket a la perfecció: les ordres són idèntiques.

I l'intercanvi:

dvc push    # puja al remote els continguts de la caché que hi faltin

De l'altra banda, Marc:

git clone <repo> && cd cineclick-churn
# ... crea l'entorn com a 02-02 ...
dvc pull    # llegeix els .dvc del commit actual i descarrega aquells continguts exactes

dvc pull mira quins hashes referencien els metafitxers presents al commit on és, els busca al remote, els baixa a la seva caché i materialitza els fitxers de treball. Marc té ara exactament els mateixos bytes amb què Laura va entrenar — no "el CSV que hi hagués aquell dia a la carpeta compartida". El gest git pull + dvc pull es converteix en el nou "posar-se al dia" del projecte.

Viatjar en el temps: una versió nova del dataset

Arriba el moment que justifica tota la maquinària. És juliol, i el data engineer lliura un extracte nou amb dues novetats: un mes addicional de clients i, atenció, la columna hores_setmana ja ve corregida a origen (van arreglar el bug dels valors negatius que Laura filtrava a mà). El fitxer es diu igual i substitueix l'anterior a data/raw/. Abans, això era una pèrdua irreversible; ara:

# El nou extracte sobreescriu el fitxer de treball
dvc status
#   data/raw/clients_churn.csv.dvc: changed  <- DVC detecta que el contingut ja no encaixa amb el hash

dvc add data/raw/clients_churn.csv        # nou hash, nova entrada a la caché
git add data/raw/clients_churn.csv.dvc
git commit -m "Dades de juliol: nou mes i hores_setmana corregida a origen"
dvc push

El metafitxer ara conté el hash de l'extracte de juliol, i el commit ho deixa escrit a la història. Les dues versions conviuen a la caché i al remote, cadascuna sota el seu hash. I aquí, el viatge en el temps — suposem que el model entrenat amb juliol es comporta estrany i volem reproduir l'entrenament de juny:

git log --oneline -- data/raw/clients_churn.csv.dvc   # localitza el commit de juny
git checkout a1b2c3d                                  # Git restaura el METAFITXER de juny...
dvc checkout                                          # ...i DVC materialitza LES DADES de juny des de la caché

git checkout només mou els fitxers que Git controla — és a dir, el .dvc amb el hash antic. dvc checkout llegeix aquell hash i reposa a data/raw/ el contingut corresponent. Dues ordres, i el directori de treball és idèntic, codi i dades, al dia de l'entrenament de juny. Per tornar al present: git checkout master && dvc checkout. Si el contingut no fos a la caché local (una màquina nova), dvc pull el portaria del remote.

Això és exactament el que el diagnòstic del mòdul 1 trobava a faltar al problema núm. 3: "no es pot reproduir el model actual ni comparar entrenaments: el dataset de la v3 ja no existeix". Ara cada dataset existeix per sempre, adreçable per commit, i la pregunta "amb quines dades es va entrenar aquest model?" té per fi una resposta mecànica: les del commit d'on va sortir.

Què versiona cadascú: Git vs. DVC

La parella queda repartida així:

Aspecte Git DVC
Què versiona Codi, configuració, tests, docs, metafitxers .dvc Dades i fitxers grans/binaris (datasets, i aviat models)
On guarda el contingut Al repositori mateix (història completa a cada clon) Caché local + remote (només es descarrega el que necessites)
Unitat de canvi Commit (diff línia a línia) Hash del contingut complet del fitxer
Com es comparteix git push / git pull (GitHub, etc.) dvc push / dvc pull (S3, GCS, carpeta de xarxa...)
Com es viatja en el temps git checkout <commit> dvc checkout (després del de Git)
Mida còmoda KB–MB, text MB–TB, qualsevol format

I la regla mnemotècnica que governa el repartiment: Git versiona les receptes; DVC, els ingredients. Cada commit de Git és una foto completa del projecte perquè conté el codi, la configuració, el lock de l'entorn... i els resguards que fixen les dades.

Errors Comuns i Consells

  • Error: commitejar el metafitxer sense fer dvc push (o a l'inrevés). Si Marc fa git pull i el hash que referencia el .dvc no és al remote, el seu dvc pull fallarà amb un eloqüent "file is missing". Els dos pushes van junts, sempre; interioritza-ho com un sol gest (al mòdul 5, la CI ho verificarà per nosaltres).
  • Error: editar un fitxer .dvc a mà. El hash és una empremta calculada; escriure-hi un altre valor no canvia les dades, només trenca la correspondència. Els .dvc els escriu DVC (dvc add); tu només els commiteges.
  • Error: fer git checkout a un commit antic i oblidar el dvc checkout. Quedes en un estat incoherent traïdor: codi de juny amb dades de juliol al directori. Els parells van junts: git checkout + dvc checkout, anada i tornada. (Existeix dvc install, que enganxa hooks de Git per automatitzar el segon pas; activa'l si et descobreixes oblidant-lo.)
  • Error: versionar amb DVC fitxers que han d'anar a Git. Un config.yaml de 20 línies a DVC és contraproduent: perds el diff, que aquí sí que és valuós. El criteri és mida + binarietat + ritme de canvi, no "tot el que hi ha a data/ per definició".
  • Consell: un dataset nou mereix un missatge de commit informatiu. "update data" no; "Dades de juliol: nou mes i hores_setmana corregida a origen" sí. L'historial de git log -- data/raw/*.dvc és la documentació de l'evolució de les teves dades — gratis.

Exercicis

Exercici 1

Ordena correctament aquesta seqüència desordenada d'ordres per incorporar un extracte nou del dataset i deixar-lo disponible per a tot l'equip, i explica què fa cada pas: git commit -m "Dades d'agost", dvc push, dvc add data/raw/clients_churn.csv, git add data/raw/clients_churn.csv.dvc, (el fitxer nou sobreescriu el vell a data/raw/).

Exercici 2

Laura va entrenar el "model bo" fa tres setmanes, al commit f4e5d6a. Avui el dataset de treball és el de juliol. Escriu la seqüència exacta d'ordres per: (1) deixar la màquina exactament com el dia d'aquell entrenament (codi i dades); (2) comprovar que les dades materialitzades estan sincronitzades amb el metafitxer; (3) tornar a l'estat actual. Indica quina ordre addicional necessitaries si ho fessis en una màquina acabada de clonar.

Exercici 3

Un company proposa: "en lloc de DVC, pugem cada extracte a la carpeta compartida com a clients_churn_2026_06.csv, clients_churn_2026_07.csv... i que el config.yaml apunti al que toqui". És un avenç sobre sobreescriure, sens dubte. Assenyala com a mínim tres garanties que dona DVC i que aquest esquema de sufixos de data no dona.

Solucions

Solució 1: (0) l'extracte nou sobreescriu el fitxer de treball — el punt de partida; (1) dvc add data/raw/clients_churn.csv — calcula el hash nou, guarda el contingut a la caché i actualitza el metafitxer; (2) git add data/raw/clients_churn.csv.dvc — prepara el resguard actualitzat per al commit; (3) git commit -m "Dades d'agost" — fixa a la història la nova versió de dades al costat del codi vigent; (4) dvc push — puja el contingut al remote perquè el dvc pull de qualsevol company funcioni. L'ordre de 3 i 4 es pot intercanviar, però cap dels dos no pot faltar.

Solució 2: (1) git checkout f4e5d6a seguit de dvc checkout — Git reposa el codi i el metafitxer d'aquell dia; DVC en llegeix el hash i materialitza aquell dataset des de la caché; (2) dvc status — ha de respondre que tot està al dia (cap discrepància entre fitxers de treball i metafitxers); (3) git checkout master i de nou dvc checkout. En una màquina acabada de clonar la caché és buida, així que entre el checkout i la feina caldria dvc pull per descarregar del remote el contingut que el hash referencia.

Solució 3: entre d'altres: (1) verificació d'integritat — el hash MD5 garanteix que el fitxer és exactament l'original; amb sufixos de data, res no impedeix que algú editi o torni a pujar _2026_06.csv i ningú no ho noti mai; (2) vincle automàtic codi↔dades — amb DVC, cada commit fixa quines dades li corresponen; amb sufixos, el vincle depèn que un humà actualitzi el config.yaml al commit correcte i que ningú no el toqui després; (3) deduplicació i transport eficient — la caché per hash no guarda dues vegades el mateix contingut i dvc pull porta només el que falta, mentre que la carpeta de fitxers amb data creix sense control i es copia a mà; (4, propina) un sol flux per a qualsevol mida i backend — el dia que el CSV siguin 40 GB a S3, les ordres no canvien.

Conclusió

El tercer artefacte ha entrat a ratlla: amb dvc init, dvc add i un remote configurat, cada versió de clients_churn.csv queda fixada pel seu hash en un metafitxer commitejat, compartida via dvc push/dvc pull, i recuperable per sempre amb la parella git checkout + dvc checkout — el problema núm. 3 del diagnòstic està resolt. Ara la foto és: codi versionat, entorn versionat, dades versionades... però el procés que els uneix continua sent artesanal: executar la neteja, després les features, després l'entrenament, després l'avaluació, a mà i en l'ordre correcte, recordant què cal repetir quan alguna cosa canvia. Aquest encadenament manual és fràgil, i DVC té una segona cara pensada just per eliminar-lo: els pipelines declarats a dvc.yaml, on cada etapa coneix les seves dependències i dvc repro re-executa només el necessari. És la peça que tanca el mòdul, i amb ella CineClick tocarà per primera vegada la reproduïbilitat completa: la veiem a la propera lliçó.

© Copyright 2026. Tots els drets reservats