Asosiy qismga o'tish

Ma'lumot versiyalash: qaysi ma'lumotda o'qitilgan?

5-bob 13 kod bloki 3111 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

Sud zalidagi savol

Tasavvur qiling, sizdan rasmiy tushuntirish so'raldi.

Bank modelingiz mijozga kredit bermadi. Mijoz shikoyat qildi. Tekshiruvchi keldi va savol berdi:

— Bu qaror qaysi ma'lumot asosida chiqarilgan?

Siz javob berasiz: — mijozlar.csv faylidan.

— Yaxshi. O'sha faylni ko'rsating. Aynan qaror chiqarilgan kundagi holatini.

Va shu yerda jim qolasiz. Chunki fayl bor — lekin u o'shandan beri o'n marta yangilangan. Nomi o'sha. Ichi boshqa.

Siz tekshiruvchiga "bilmayman" deb aytolmaysiz. Lekin haqiqat shu: bilmaysiz.

Kod bilan ma'lumot o'rtasidagi tengsizlik

Diqqat qiling, kod bilan bunday muammo bo'lmaydi.

Kodni git saqlaydi. "O'sha kunda kod qanday edi?" degan savolga javob bir buyruqda: git checkout <sana>. Har o'zgarish yozilgan, kim qilgani ma'lum, qaytish mumkin.

Ma'lumot bilan esa odatda hech narsa yo'q. ijara.csv faylini kimdir yangiladi — nomi o'sha bo'lib qoldi, eski nusxa esa ustidan yozildi.

Bu tengsizlik g'alati, chunki 1-bobda ko'rdik: natijaga ma'lumot kamida kod qadar ta'sir qiladi. Lekin biz kodni qattiq nazorat qilamiz va ma'lumotni umuman qilmaymiz.

Natija — uchta savolga javob yo'q:

  • Bu model qaysi ma'lumotda o'qitilgan?
  • Uni bugun qayta o'qitsam, o'sha natija chiqadimi?
  • O'tgan oy kimdir ma'lumotni o'zgartirdimi?

Bu bob shu uchalasiga javob quradi. Boshlaymiz — avval muammoni ko'rsatamiz:

python23 qator
import shutil
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score

XUS = ["xonalar", "maydon", "qavat", "jami_qavat", "narx_mln", "tavsif_uzunligi",
       "rasmlar_soni", "telefon_takrorlanishi", "kunlik_elonlar", "egasi_tasdiqlangan"]

ISH.mkdir(parents=True, exist_ok=True)
ish_fayl = ISH / "ijara_ishchi.csv"
shutil.copy(DATA / "ijara.csv", ish_fayl)

def oqit(yol):
    d = pd.read_csv(yol)
    Xtr, Xte, ytr, yte = train_test_split(d[XUS], d["broker"], test_size=0.25,
                                          stratify=d["broker"], random_state=42)
    m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3,
                               random_state=42, n_jobs=1).fit(Xtr, ytr)
    return round(f1_score(yte, m.predict(Xte)), 4), len(d)

f1_avval, n_avval = oqit(ish_fayl)
print(f"BIRINCHI o'qitish : F1 = {f1_avval}  ({n_avval:,} qator)")
Chiqish
BIRINCHI o'qitish : F1 = 0.8603  (12,000 qator)

Endi kimdir ma'lumotni "yaxshiladi" — shubhali qatorlarni tozaladi. Niyati yaxshi:

python10 qator
d = pd.read_csv(ish_fayl)
CHEGARA = 10.2                                # 90-foizli: eng qimmat 10%
tozalangan = d[d["narx_mln"] < CHEGARA]       # "juda qimmat" e'lonlarni olib tashladi
tozalangan.to_csv(ish_fayl, index=False)      # O'SHA nomga yozdi

f1_keyin, n_keyin = oqit(ish_fayl)
print(f"IKKINCHI o'qitish : F1 = {f1_keyin}  ({n_keyin:,} qator)")
print(f"\nfayl nomi o'zgardimi : yo'q, {ish_fayl.name}")
print(f"qatorlar o'zgardimi  : {n_avval:,} -> {n_keyin:,}  ({n_avval - n_keyin} qator o'chdi)")
print(f"F1 o'zgardimi        : {f1_avval} -> {f1_keyin}")
Chiqish
IKKINCHI o'qitish : F1 = 0.869  (10,794 qator)

fayl nomi o'zgardimi : yo'q, ijara_ishchi.csv
qatorlar o'zgardimi  : 12,000 -> 10,794  (1206 qator o'chdi)
F1 o'zgardimi        : 0.8603 -> 0.869

Ikki xil natija, bitta fayl nomi. Qaysi biri "to'g'ri"? Uch oydan keyin buni aniqlab bo'lmaydi.

Yechim 1: manifest (barmoq izlari ro'yxati)

2-bobdagi hash ni eslang. Endi uni ro'yxatga aylantiramiz: qaysi fayl, qanday hash, qancha qator, qaysi ustunlar.

shu faylni yaratingsrc/quvur/manifest.py

python79 qator
"""Ma'lumot manifesti: qaysi fayl, qanday mazmun, qancha qator.

Git kodni saqlaydi; manifest esa ma'lumotning BARMOQ IZINI saqlaydi.
Faylning o'zi git ga tushmaydi (katta), lekin uning izi tushadi.
"""
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import pandas as pd


def fayl_hash(yol: str | Path, blok: int = 1 << 20) -> str:
    h = hashlib.sha256()
    with open(yol, "rb") as f:
        while chunk := f.read(blok):
            h.update(chunk)
    return h.hexdigest()


def tavsifla(yol: str | Path) -> dict[str, Any]:
    """Bitta ma'lumot faylining to'liq tavsifi."""
    p = Path(yol)
    d = pd.read_parquet(p) if p.suffix == ".parquet" else pd.read_csv(p)
    return {
        "yol": str(p.name),
        "sha256": fayl_hash(p),
        "hajm_bayt": p.stat().st_size,
        "qatorlar": int(len(d)),
        "ustunlar": list(d.columns),
        "turlar": {k: str(v) for k, v in d.dtypes.items()},
        "yozilgan": datetime.now(timezone.utc).isoformat(timespec="seconds"),
    }


def manifest(yollar: list[str | Path]) -> dict[str, Any]:
    return {"versiya": 1, "fayllar": [tavsifla(y) for y in yollar]}


def saqla(m: dict[str, Any], yol: str | Path) -> None:
    Path(yol).write_text(json.dumps(m, ensure_ascii=False, indent=2), encoding="utf-8")


def yukla(yol: str | Path) -> dict[str, Any]:
    return json.loads(Path(yol).read_text(encoding="utf-8"))


def solishtir(eski: dict[str, Any], yangi: dict[str, Any]) -> list[str]:
    """Ikki manifest orasidagi farqlarni odam o'qiydigan ro'yxat qilib qaytaradi."""
    farqlar: list[str] = []
    e = {f["yol"]: f for f in eski["fayllar"]}
    y = {f["yol"]: f for f in yangi["fayllar"]}

    for nom in sorted(set(e) | set(y)):
        if nom not in y:
            farqlar.append(f"{nom}: O'CHIRILGAN")
            continue
        if nom not in e:
            farqlar.append(f"{nom}: YANGI ({y[nom]['qatorlar']:,} qator)")
            continue
        a, b = e[nom], y[nom]
        if a["sha256"] == b["sha256"]:
            continue
        farqlar.append(f"{nom}: MAZMUN O'ZGARGAN")
        if a["qatorlar"] != b["qatorlar"]:
            farq = b["qatorlar"] - a["qatorlar"]
            farqlar.append(f"    qatorlar: {a['qatorlar']:,} -> {b['qatorlar']:,} ({farq:+,})")
        qoshilgan = set(b["ustunlar"]) - set(a["ustunlar"])
        yoqolgan = set(a["ustunlar"]) - set(b["ustunlar"])
        if qoshilgan:
            farqlar.append(f"    yangi ustunlar: {sorted(qoshilgan)}")
        if yoqolgan:
            farqlar.append(f"    yo'qolgan ustunlar: {sorted(yoqolgan)}")
        for u in sorted(set(a["turlar"]) & set(b["turlar"])):
            if a["turlar"][u] != b["turlar"][u]:
                farqlar.append(f"    '{u}' turi: {a['turlar'][u]} -> {b['turlar'][u]}")
    return farqlar

Endi o'sha stsenariyni manifest bilan takrorlaymiz:

python19 qator
python3 -c "
import sys, shutil; sys.path.insert(0, 'src')
from pathlib import Path
import pandas as pd
from quvur.manifest import manifest, saqla, yukla, solishtir

shutil.copy('../datasets/ijara.csv', 'ijara_ishchi.csv')
saqla(manifest(['ijara_ishchi.csv']), 'manifest_v1.json')
print('v1 manifest yozildi')

d = pd.read_csv('ijara_ishchi.csv')
d[d['narx_mln'] < 10.2].to_csv('ijara_ishchi.csv', index=False)
saqla(manifest(['ijara_ishchi.csv']), 'manifest_v2.json')
print('v2 manifest yozildi')
print()
print('FARQLAR:')
for q in solishtir(yukla('manifest_v1.json'), yukla('manifest_v2.json')):
    print(' ', q)
"
Chiqish (qaytish kodi 0, 827 ms)
v1 manifest yozildi
v2 manifest yozildi

FARQLAR:
  ijara_ishchi.csv: MAZMUN O'ZGARGAN
      qatorlar: 12,000 -> 10,794 (-1,206)

Endi o'zgarish ko'rinadi. Manifest git ga tushadi (u kichik), ma'lumot esa tushmaydi.

Ustun qo'shilsa yoki tur o'zgarsa ham ushlaydi:

python14 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
import pandas as pd
from quvur.manifest import manifest, saqla, yukla, solishtir

d = pd.read_csv('ijara_ishchi.csv')
d['yangi_belgi'] = d['narx_mln'] / d['maydon']     # yangi ustun
d['xonalar'] = d['xonalar'].astype(float)          # tur o'zgardi
d.to_csv('ijara_ishchi.csv', index=False)
saqla(manifest(['ijara_ishchi.csv']), 'manifest_v3.json')

for q in solishtir(yukla('manifest_v2.json'), yukla('manifest_v3.json')):
    print(' ', q)
"
Chiqish (qaytish kodi 0, 614 ms)
  ijara_ishchi.csv: MAZMUN O'ZGARGAN
      yangi ustunlar: ['yangi_belgi']
      'xonalar' turi: int64 -> float64

Yechim 2: mazmun bo'yicha saqlash

Manifest o'zgarishni ko'rsatadi, lekin eski ma'lumotni qaytarmaydi. Buning uchun fayllarni saqlash kerak — har versiyani.

Sodda usul "ijara_v1.csv, ijara_v2.csv..." — lekin u tez chalkashadi va bir xil fayl ikki marta saqlanadi.

To'g'ri usul: faylni o'z hash i bilan saqlash. Bu content-addressed storage deyiladi va git ham xuddi shunday ishlaydi.

python15 qator
diagramma(
    {
        "ijara.csv\n(ish nomi)": (0, 1.2, 2.4, 1.0, "manba"),
        "sha256 hisoblanadi": (3.2, 1.2, 2.6, 1.0, "qayta"),
        "ombor/ab/cd1234...\n(mazmun bo'yicha)": (6.6, 0, 2.8, 1.0, "saqlash"),
        "manifest.json\n(nom -> hash)": (6.6, 2.4, 2.8, 1.0, "saqlash"),
    },
    [
        ("ijara.csv\n(ish nomi)", "sha256 hisoblanadi"),
        ("sha256 hisoblanadi", "ombor/ab/cd1234...\n(mazmun bo'yicha)", "nusxa"),
        ("sha256 hisoblanadi", "manifest.json\n(nom -> hash)", "yozuv"),
    ],
    nom="mazmun_ombori.png", kenglik=12,
    izoh="Fayl o'z hash i bilan saqlanadi. Bir xil mazmun — bir xil joy, ikki marta saqlanmaydi.",
)

Fayl o'z hash i bilan saqlanadi. Bir xil mazmun — bir xil joy, ikki marta saqlanmaydi.

Fayl o'z hash i bilan saqlanadi. Bir xil mazmun — bir xil joy, ikki marta saqlanmaydi.

shu faylni yaratingsrc/quvur/ombor.py

python43 qator
"""Mazmun bo'yicha saqlash (content-addressed storage).

Fayl o'z sha256 hash i bilan saqlanadi. Ikki xil nom bir xil mazmunga ega bo'lsa,
disk da bitta nusxa yotadi — git ham shunday ishlaydi.
"""
import shutil
from pathlib import Path

from .manifest import fayl_hash


class Ombor:
    def __init__(self, ildiz: str | Path = ".ombor") -> None:
        self.ildiz = Path(ildiz)
        self.ildiz.mkdir(parents=True, exist_ok=True)

    def _yol(self, h: str) -> Path:
        # birinchi 2 belgi — papka (bitta papkada million fayl bo'lmasin)
        return self.ildiz / h[:2] / h[2:]

    def qoy(self, fayl: str | Path) -> tuple[str, bool]:
        """Faylni omborga qo'yadi. (hash, yangi_edimi) qaytaradi."""
        h = fayl_hash(fayl)
        nishon = self._yol(h)
        if nishon.exists():
            return h, False                      # allaqachon bor — nusxalamaymiz
        nishon.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(fayl, nishon)
        return h, True

    def ol(self, h: str, nishon: str | Path) -> Path:
        """Hash bo'yicha faylni tiklaydi."""
        manba = self._yol(h)
        if not manba.exists():
            raise FileNotFoundError(f"omborda yo'q: {h[:12]}...")
        Path(nishon).parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(manba, nishon)
        return Path(nishon)

    def hajmi(self) -> tuple[int, int]:
        """(fayllar soni, jami bayt)."""
        fayllar = [p for p in self.ildiz.rglob("*") if p.is_file()]
        return len(fayllar), sum(p.stat().st_size for p in fayllar)
python31 qator
python3 -c "
import sys, shutil; sys.path.insert(0, 'src')
from pathlib import Path
import pandas as pd
from quvur.ombor import Ombor

o = Ombor('.ombor')

# v1 ni qo'yamiz
shutil.copy('../datasets/ijara.csv', 'ijara_ishchi.csv')
h1, yangi1 = o.qoy('ijara_ishchi.csv')
print(f'v1 saqlandi: {h1[:12]}...  yangi={yangi1}')

# o'sha faylni YANA qo'yamiz — nusxalanmasligi kerak
h1b, yangi1b = o.qoy('ijara_ishchi.csv')
print(f'v1 qayta   : {h1b[:12]}...  yangi={yangi1b}   <- nusxalanmadi')

# v2: o'zgartiramiz
d = pd.read_csv('ijara_ishchi.csv')
d[d['narx_mln'] < 10.2].to_csv('ijara_ishchi.csv', index=False)
h2, yangi2 = o.qoy('ijara_ishchi.csv')
print(f'v2 saqlandi: {h2[:12]}...  yangi={yangi2}')

soni, bayt = o.hajmi()
print(f'\nomborda: {soni} fayl, {bayt/1024:.0f} KB')

# v1 ni TIKLAYMIZ
o.ol(h1, 'tiklangan.csv')
t = pd.read_csv('tiklangan.csv')
print(f'v1 tiklandi: {len(t):,} qator (hozirgi ish fayli: {len(pd.read_csv(\"ijara_ishchi.csv\")):,})')
"
Chiqish (qaytish kodi 0, 801 ms)
v1 saqlandi: 03dbeed95e47...  yangi=True
v1 qayta   : 03dbeed95e47...  yangi=False   <- nusxalanmadi
v2 saqlandi: d494a60f4959...  yangi=True

omborda: 2 fayl, 1336 KB
v1 tiklandi: 12,000 qator (hozirgi ish fayli: 10,794)

Uchta muhim narsa ko'rindi:

  • Bir xil faylni ikki marta qo'yganda ikkinchi marta nusxalanmadi (yangi=False)
  • Har versiya o'z hash i bilan yonma-yon yashaydi
  • Eski versiya tiklandi — fayl nomi bir xil bo'lsa ham

DVC: sanoat standarti

Yuqoridagi ikki g'oya — manifest va mazmun ombori — aynan DVC (Data Version Control) qiladigan ish. Farqi: DVC git bilan birlashadi va ombor uzoqdagi joyda (S3, MinIO, DO Spaces) bo'lishi mumkin.

python1 qator
python3 -m dvc version 2>/dev/null | head -6 || echo "DVC o'rnatilmagan (pip install dvc)"
Chiqish (qaytish kodi 0, 1081 ms)
DVC version: 3.67.1 (pip)
-------------------------
Platform: Python 3.10.12 on Linux-5.15.167.4-microsoft-standard-WSL2-x86_64-with-glibc2.35
Subprojects:
	dvc_data = 3.18.3
	dvc_objects = 5.2.0

DVC ning asosiy ish oqimi:

python10 qator
import pandas as pd
show(pd.DataFrame([
    {"buyruq": "dvc init", "nima qiladi": "Git omborida DVC ni yoqadi"},
    {"buyruq": "dvc add malumot/ijara.csv", "nima qiladi": "Faylni kuzatuvga oladi, .dvc izi yasaydi"},
    {"buyruq": "git add malumot/ijara.csv.dvc .gitignore", "nima qiladi": "IZ git ga tushadi, fayl emas"},
    {"buyruq": "dvc remote add -d ombor s3://...", "nima qiladi": "Uzoq omborni ulaydi (MinIO, DO Spaces)"},
    {"buyruq": "dvc push", "nima qiladi": "Ma'lumotni omborga yuboradi"},
    {"buyruq": "git checkout <eski-commit>", "nima qiladi": "Kod eski holatga qaytadi"},
    {"buyruq": "dvc checkout", "nima qiladi": "MA'LUMOT ham o'sha holatga qaytadi"},
]))
buyruqnima qiladi
dvc initGit omborida DVC ni yoqadi
dvc add malumot/ijara.csvFaylni kuzatuvga oladi, .dvc izi yasaydi
git add malumot/ijara.csv.dvc .gitignoreIZ git ga tushadi, fayl emas
dvc remote add -d ombor s3://...Uzoq omborni ulaydi (MinIO, DO Spaces)
dvc pushMa'lumotni omborga yuboradi
git checkout <eski-commit>Kod eski holatga qaytadi
dvc checkoutMA'LUMOT ham o'sha holatga qaytadi

Oxirgi ikki qator eng muhimi: git checkout kodni, dvc checkout esa ma'lumotni qaytaradi. Ikkalasi birga — loyihaning o'sha kungi to'liq holati.

.dvc fayli ichida nima bor — bizning manifestimizga juda o'xshash:

python15 qator
cat > namuna.csv <<'CSV'
ism,baho
Aziz,85
Bobur,92
CSV
python3 -c "
import sys; sys.path.insert(0, 'src')
from quvur.manifest import fayl_hash
print('# namuna.csv.dvc fayli taxminan shunday ko\'rinadi:')
print('outs:')
print(f'- md5: {fayl_hash(\"namuna.csv\")[:32]}')
print(f'  size: {__import__(\"pathlib\").Path(\"namuna.csv\").stat().st_size}')
print('  path: namuna.csv')
"
rm -f namuna.csv
Chiqish (qaytish kodi 0, 423 ms)
# namuna.csv.dvc fayli taxminan shunday ko'rinadi:
outs:
- md5: 40c9c16a159eacb6e4245208693ce1f2
  size: 26
  path: namuna.csv

Ma'lumotni git ga qo'ymang

Git har versiyani to'liq saqlaydi va tarixni hech qachon unutmaydi. 500 MB li CSV ni bir marta commit qilsangiz, uni o'chirsangiz ham repo abadiy 500 MB bo'lib qoladi — git filter-repo bilan tarixni qayta yozmaguningizcha.

Qoida: git ga faqat iz (.dvc, manifest.json), ma'lumotning o'zi alohida omborda.

Qaysi yondashuvni tanlash

python11 qator
import pandas as pd
show(pd.DataFrame([
    {"holat": "Ma'lumot kichik (< 10 MB), kam o'zgaradi", "yechim": "To'g'ridan-to'g'ri git ga",
     "murakkablik": "yo'q"},
    {"holat": "O'rta hajm, bitta jamoa", "yechim": "Manifest + mazmun ombori (yuqoridagi kod)",
     "murakkablik": "past"},
    {"holat": "Katta, ko'p jamoa, uzoq ombor", "yechim": "DVC yoki LakeFS",
     "murakkablik": "o'rtacha"},
    {"holat": "Jadval, ko'p yozuvchi, time travel", "yechim": "Iceberg (19-bob)",
     "murakkablik": "yuqori"},
]))
holatyechimmurakkablik
Ma'lumot kichik (< 10 MB), kam o'zgaradiTo'g'ridan-to'g'ri git gayo'q
O'rta hajm, bitta jamoaManifest + mazmun ombori (yuqoridagi kod)past
Katta, ko'p jamoa, uzoq omborDVC yoki LakeFSo'rtacha
Jadval, ko'p yozuvchi, time travelIceberg (19-bob)yuqori

Ko'p jamoa ikkinchi qatordan boshlashi mumkin. DVC ni "hamma ishlatadi" degani uchun emas, kerak bo'lganda qo'shing.

O'qitishni manifestga bog'laymiz

Endi 2-bobdagi pasportni manifest bilan birlashtiramiz — o'qitish natijasi ma'lumot versiyasiga bog'langan bo'lsin:

python35 qator
python3 -c "
import sys, json, shutil; sys.path.insert(0, 'src')
from pathlib import Path
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from quvur.manifest import tavsifla
from quvur.ombor import Ombor

XUS = ['xonalar','maydon','qavat','jami_qavat','narx_mln','tavsif_uzunligi',
       'rasmlar_soni','telefon_takrorlanishi','kunlik_elonlar','egasi_tasdiqlangan']

shutil.copy('../datasets/ijara.csv', 'ijara_ishchi.csv')
o = Ombor('.ombor')
h, _ = o.qoy('ijara_ishchi.csv')

d = pd.read_csv('ijara_ishchi.csv')
Xtr, Xte, ytr, yte = train_test_split(d[XUS], d['broker'], test_size=0.25,
                                      stratify=d['broker'], random_state=42)
m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3, random_state=42, n_jobs=1).fit(Xtr, ytr)

yozuv = {
    'malumot_hash': h,
    'malumot': tavsifla('ijara_ishchi.csv'),
    'metrika': {'f1': round(f1_score(yte, m.predict(Xte)), 4)},
}
Path('oqitish_yozuvi.json').write_text(json.dumps(yozuv, ensure_ascii=False, indent=2), encoding='utf-8')
print('ma\'lumot hash :', h[:16], '...')
print('qatorlar      :', yozuv['malumot']['qatorlar'])
print('F1            :', yozuv['metrika']['f1'])
print()
print('=> Bu F1 AYNAN shu hash li ma\'lumotga tegishli.')
print('   Ombor bor ekan, istalgan paytda o\'sha ma\'lumotni tiklab, natijani tekshirish mumkin.')
"
Chiqish (qaytish kodi 0, 1758 ms)
ma'lumot hash : 03dbeed95e47e74c ...
qatorlar      : 12000
F1            : 0.8603

=> Bu F1 AYNAN shu hash li ma'lumotga tegishli.
   Ombor bor ekan, istalgan paytda o'sha ma'lumotni tiklab, natijani tekshirish mumkin.

Mana takrorlanishning ikkinchi darajasi (2-bobdagi jadvalni eslang): endi nafaqat seed, balki ma'lumotning o'zi ham qulflangan.

Xulosa

Tushuncha Asosiy fikr
Asosiy muammo Fayl nomi o'sha, mazmuni boshqa — model natijasi o'zgaradi
Manifest Hash + qator + ustun + turlar; git ga tushadi, ma'lumot tushmaydi
solishtir Qator soni, yangi/yo'qolgan ustun, tur o'zgarishi — hammasi ko'rinadi
Mazmun ombori Fayl o'z hash i bilan saqlanadi; takror nusxa saqlanmaydi
Tiklash Eski versiya nomi bir xil bo'lsa ham qaytariladi
DVC git checkout kodni, dvc checkout ma'lumotni qaytaradi
Git ga qo'ymang 500 MB commit repo da abadiy qoladi
Bog'lash O'qitish yozuvida ma'lumot hash i bo'lsin

Talaba ishi

  1. solishtir ga yangi tekshiruv qo'shing: ustun tartibi o'zgarganini aniqlasin. Nega bu muhim? (Maslahat: 3-bob.)
  2. Ombor.qoy bir xil mazmunli, lekin boshqa nomli ikki faylga nima qiladi? Sinab ko'ring.
  3. Ombor ga tozala(saqlanadigan_hashlar) metodini yozing — manifestlarda ishlatilmayotgan fayllarni o'chirsin. Bu qanday xavf tug'diradi?
  4. .ombor papkasining hajmini uchta versiyadan keyin o'lchang. Uch alohida nusxa saqlanganidan qancha kam?

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar