Asosiy qismga o'tish

Orkestratsiya: quvurni bosqichlarga bo'lish

8-bob 15 kod bloki 3348 so'z

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

Qirq daqiqa, keyin xato

Kechqurun o'qitish skriptini ishga tushirdingiz. U beshta ishni ketma-ket bajaradi:

python5 qator
1. ma'lumotni o'qish        (2 daqiqa)
2. tozalash                 (5 daqiqa)
3. xususiyat hisoblash      (30 daqiqa)
4. model o'qitish           (15 daqiqa)
5. baholash va saqlash      (1 daqiqa)

Qirq daqiqa kutdingiz. Ekranda xato chiqdi:

yaml1 qator
4-bosqich: ValueError

Endi nima qilasiz? Skriptni qaytadan ishga tushirasiz. U esa birinchi bosqichdan boshlaydi. Yana 37 daqiqa — allaqachon muvaffaqiyatli bajarilgan ishni qaytadan qilish uchun.

Xato beshinchi urinishda tuzalsa, siz uch soat yo'qotasiz. Ishning o'zi esa 15 daqiqalik.

Ovqat pishirish va idish yuvish

Muammoni boshqacha ko'ring. Oshpaz osh damlayapti:

  • sabzi to'g'raladi
  • go'sht qovuriladi
  • guruch solinadi

Uchinchi qadamda guruch tugab qolsa, oshpaz sabzini qayta to'g'ramaydi. To'g'ralgan sabzi joyida turibdi. U faqat guruch olib keladi va o'sha joydan davom etadi.

Skriptingiz esa buni qila olmaydi, chunki u bosqichlarni ajratmagan — u bitta uzun ish.

Yechim: quvurni bosqichlarga bo'lish va har bosqichning natijasini saqlab qo'yish. Shunda qayta ishga tushirish faqat buzilgan joydan davom etadi.

Bu orkestratsiya deyiladi.

Bitta skript nima uchun yetarli emas

Hozirgacha o'qitish bitta skriptda edi: o'qi → tayyorla → o'qit → saqla. Ma'lumot o'sganda va bosqichlar ko'payganda bu tuzilish buziladi.

python11 qator
import pandas as pd
show(pd.DataFrame([
    {"muammo": "Oxirgi bosqichda yiqildi", "bitta skriptda": "Hammasi boshidan qayta ishlaydi",
     "orkestratsiyada": "Faqat yiqilgan bosqichdan davom etadi"},
    {"muammo": "Faqat modelni almashtirmoqchisiz", "bitta skriptda": "Ma'lumot ham qayta tayyorlanadi",
     "orkestratsiyada": "Tayyorlash keshdan olinadi"},
    {"muammo": "Ikki bosqich bir-biriga bog'liq emas", "bitta skriptda": "Ketma-ket bajariladi",
     "orkestratsiyada": "Parallel bajarilishi mumkin"},
    {"muammo": "Kecha nima bo'lgan edi?", "bitta skriptda": "Jurnalda qidirasiz",
     "orkestratsiyada": "Har bosqichning holati saqlanadi"},
]))
muammobitta skriptdaorkestratsiyada
Oxirgi bosqichda yiqildiHammasi boshidan qayta ishlaydiFaqat yiqilgan bosqichdan davom etadi
Faqat modelni almashtirmoqchisizMa'lumot ham qayta tayyorlanadiTayyorlash keshdan olinadi
Ikki bosqich bir-biriga bog'liq emasKetma-ket bajariladiParallel bajarilishi mumkin
Kecha nima bo'lgan edi?Jurnalda qidirasizHar bosqichning holati saqlanadi

Quvur — bu graf

Endi eng muhim tushunchaga keldik, va uning nomi qo'rqinchli ko'rinadi: DAG.

Qo'rqmang — siz uni allaqachon bilasiz.

Osh damlashni eslang. Bosqichlar orasida bog'liqlik bor:

  • Sabzini to'g'ramaguncha qozonga sololmaysiz.
  • Go'shtni qovurmaguncha suv quyolmaysiz.
  • Lekin sabzi to'g'rash va piyoz to'g'rash — bir-biriga bog'liq emas. Ikki kishi bir vaqtda qila oladi.

Mana shu ikki qoida DAG ni tashkil qiladi:

D — Directed (yo'naltirilgan). Bog'liqlikning yo'nalishi bor. Sabzi → qozon, teskarisi emas.

A — Acyclic (siklsiz). Aylana bo'lmaydi. "A B ni kutadi, B esa A ni kutadi" degan holat imkonsiz — aks holda ikkalasi ham abadiy kutadi.

G — Graph (graf). Bu shunchaki "nuqtalar va ularni bog'lovchi chiziqlar" degani.

Ya'ni DAG — bu ish rejasi: nima nimadan keyin kelishi va nima parallel bo'lishi mumkinligi yozilgan chizma. Boshqa hech narsa emas.

Nega bu foydali? Chunki reja yozib qo'yilgan bo'lsa, dastur uni o'qiy oladi:

  • qaysi bosqichni qayta ishlatish kerakligini o'zi hisoblaydi;
  • bog'liq bo'lmaganlarni parallel ishga tushiradi;
  • yiqilganda qayerdan davom etishni biladi.

Bitta uzun skriptda bu reja hech qayerda yozilmagan — u faqat qatorlar tartibida yashiringan, va dastur uni ko'ra olmaydi.

python17 qator
diagramma(
    {
        "1. oqi\n(CSV -> parquet)": (0, 2.0, 2.5, 1.0, "manba"),
        "2. tekshir\n(sxema, sifat)": (3.2, 2.0, 2.5, 1.0, "qayta"),
        "3. tayyorla\n(xususiyatlar)": (6.4, 2.0, 2.5, 1.0, "qayta"),
        "4. oqit\n(model)": (9.6, 0.8, 2.5, 1.0, "qayta"),
        "5. baho\n(metrikalar)": (9.6, 3.2, 2.5, 1.0, "kuzatuv"),
    },
    [
        ("1. oqi\n(CSV -> parquet)", "2. tekshir\n(sxema, sifat)"),
        ("2. tekshir\n(sxema, sifat)", "3. tayyorla\n(xususiyatlar)"),
        ("3. tayyorla\n(xususiyatlar)", "4. oqit\n(model)"),
        ("3. tayyorla\n(xususiyatlar)", "5. baho\n(metrikalar)"),
    ],
    nom="quvur_dag.png", kenglik=13,
    izoh="Har bosqich o'z chiqishini yozadi. Keyingi bosqich o'sha faylni kirish sifatida oladi.",
)

Har bosqich o'z chiqishini yozadi. Keyingi bosqich o'sha faylni kirish sifatida oladi.

Har bosqich o'z chiqishini yozadi. Keyingi bosqich o'sha faylni kirish sifatida oladi.

Muhim qoida: har bosqich faylga yozadi. Shunda bosqich mustaqil bo'ladi — uni alohida ishga tushirish, keshlash va qayta bajarish mumkin.

Oddiy DAG ishga tushirgich

Airflow ni o'rnatishdan oldin g'oyani 80 qatorda yozamiz. Shunda keyin Airflow nimani avtomatlashtirayotgani aniq bo'ladi.

shu faylni yaratingsrc/quvur/dag.py

python87 qator
"""Oddiy DAG ishga tushirgich: bog'liqlik, keshlash, xato boshqaruvi.

Airflow shuni katta miqyosda qiladi. Bu yerda g'oyani ko'rish uchun eng kichik variant.
"""
import json
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable


@dataclass
class Bosqich:
    nom: str
    ish: Callable[[], Any]
    kirish: tuple[str, ...] = ()              # kerak bo'ladigan fayllar
    chiqish: tuple[str, ...] = ()             # yasaydigan fayllar
    bogliq: tuple[str, ...] = ()              # oldin bajarilishi kerak bo'lgan bosqichlar


@dataclass
class Natija:
    holat: str                                # "bajarildi" | "keshdan" | "yiqildi" | "o'tkazildi"
    vaqt_s: float = 0.0
    xato: str = ""


def _yangimi(kirish: tuple[str, ...], chiqish: tuple[str, ...]) -> bool:
    """Chiqish fayllari kirishdan yangimi? Yangi bo'lsa qayta bajarish shart emas."""
    if not chiqish:
        return False
    ch = [Path(c) for c in chiqish]
    if not all(c.exists() for c in ch):
        return False
    ki = [Path(k) for k in kirish if Path(k).exists()]
    if not ki:
        return True
    return min(c.stat().st_mtime for c in ch) >= max(k.stat().st_mtime for k in ki)


def tartibla(bosqichlar: list[Bosqich]) -> list[Bosqich]:
    """Bog'liqlik bo'yicha tartiblaydi (topologik). Sikl bo'lsa xato beradi."""
    qolgan = {b.nom: b for b in bosqichlar}
    tayyor: list[Bosqich] = []
    korilgan: set[str] = set()
    while qolgan:
        ozod = [b for b in qolgan.values() if all(d in korilgan for d in b.bogliq)]
        if not ozod:
            raise ValueError(f"sikl yoki yetishmayotgan bog'liqlik: {sorted(qolgan)}")
        for b in sorted(ozod, key=lambda x: x.nom):
            tayyor.append(b)
            korilgan.add(b.nom)
            del qolgan[b.nom]
    return tayyor


def ishga_tushir(bosqichlar: list[Bosqich], majburiy: bool = False,
                 holat_yoli: str | Path = "quvur_holati.json") -> dict[str, Natija]:
    """DAG ni bajaradi. Bosqich yiqilsa, unga bog'liqlar O'TKAZIB YUBORILADI."""
    natijalar: dict[str, Natija] = {}
    for b in tartibla(bosqichlar):
        yiqilgan = [d for d in b.bogliq
                    if natijalar.get(d) and natijalar[d].holat in {"yiqildi", "o'tkazildi"}]
        if yiqilgan:
            natijalar[b.nom] = Natija("o'tkazildi", xato=f"bog'liq yiqildi: {yiqilgan[0]}")
            continue
        if not majburiy and _yangimi(b.kirish, b.chiqish):
            natijalar[b.nom] = Natija("keshdan")
            continue
        t0 = time.perf_counter()
        try:
            b.ish()
            natijalar[b.nom] = Natija("bajarildi", round(time.perf_counter() - t0, 3))
        except Exception as e:
            natijalar[b.nom] = Natija("yiqildi", round(time.perf_counter() - t0, 3),
                                      f"{type(e).__name__}: {e}")
    Path(holat_yoli).write_text(
        json.dumps({k: v.__dict__ for k, v in natijalar.items()}, ensure_ascii=False, indent=2),
        encoding="utf-8")
    return natijalar


def chop_et(natijalar: dict[str, Natija]) -> None:
    BELGI = {"bajarildi": "+", "keshdan": "=", "yiqildi": "X", "o'tkazildi": "-"}
    for nom, n in natijalar.items():
        vaqt = f"{n.vaqt_s:6.2f}s" if n.vaqt_s else "      -"
        print(f"  [{BELGI[n.holat]}] {nom:12} {n.holat:12} {vaqt}  {n.xato}")

Quvurni yozamiz

shu faylni yaratingquvur_ishga.py

python81 qator
"""hisobot quvuri: oqi -> tekshir -> tayyorla -> oqit -> baho."""
import json
import sys
from pathlib import Path

sys.path.insert(0, "src")

import joblib
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score, precision_score, recall_score

from quvur.dag import Bosqich, chop_et, ishga_tushir
from quvur.sxema import IJARA_SXEMASI, tekshir as sxema_tekshir
from quvur.xususiyat import XUSUSIYATLAR, tayyorla

XOM = "../datasets/ijara.csv"
ORALIQ = Path("oraliq")
ORALIQ.mkdir(exist_ok=True)

P_XOM = ORALIQ / "xom.parquet"
P_TOZA = ORALIQ / "toza.parquet"
P_XUS = ORALIQ / "xususiyat.parquet"
P_MODEL = ORALIQ / "model.joblib"
P_BAHO = ORALIQ / "baho.json"


def qadam_oqi():
    pd.read_csv(XOM).to_parquet(P_XOM, index=False)


def qadam_tekshir():
    toza, karantin, _ = sxema_tekshir(pd.read_parquet(P_XOM), IJARA_SXEMASI)
    toza.to_parquet(P_TOZA, index=False)
    if len(karantin):
        karantin.to_parquet(ORALIQ / "karantin.parquet", index=False)


def qadam_tayyorla():
    d = pd.read_parquet(P_TOZA)
    x = tayyorla(d)
    x["broker"] = d["broker"].values
    x.to_parquet(P_XUS, index=False)


def qadam_oqit():
    d = pd.read_parquet(P_XUS)
    Xtr, _, ytr, _ = train_test_split(d[XUSUSIYATLAR], 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)
    joblib.dump(m, P_MODEL)


def qadam_baho():
    d = pd.read_parquet(P_XUS)
    _, Xte, _, yte = train_test_split(d[XUSUSIYATLAR], d["broker"], test_size=0.25,
                                      stratify=d["broker"], random_state=42)
    m = joblib.load(P_MODEL)
    p = m.predict(Xte)
    P_BAHO.write_text(json.dumps({
        "f1": round(float(f1_score(yte, p)), 4),
        "aniqlik": round(float(precision_score(yte, p)), 4),
        "toliqlik": round(float(recall_score(yte, p)), 4),
    }, indent=2), encoding="utf-8")


BOSQICHLAR = [
    Bosqich("oqi", qadam_oqi, kirish=(XOM,), chiqish=(str(P_XOM),)),
    Bosqich("tekshir", qadam_tekshir, kirish=(str(P_XOM),), chiqish=(str(P_TOZA),), bogliq=("oqi",)),
    Bosqich("tayyorla", qadam_tayyorla, kirish=(str(P_TOZA),), chiqish=(str(P_XUS),), bogliq=("tekshir",)),
    Bosqich("oqit", qadam_oqit, kirish=(str(P_XUS),), chiqish=(str(P_MODEL),), bogliq=("tayyorla",)),
    Bosqich("baho", qadam_baho, kirish=(str(P_XUS), str(P_MODEL)), chiqish=(str(P_BAHO),), bogliq=("oqit",)),
]

if __name__ == "__main__":
    majburiy = "--majburiy" in sys.argv
    chop_et(ishga_tushir(BOSQICHLAR, majburiy=majburiy))
    if P_BAHO.exists():
        print("\nmetrikalar:", P_BAHO.read_text(encoding="utf-8").replace("\n", " "))

Birinchi marta ishga tushiramiz — hamma bosqich bajarilishi kerak:

matn2 qator
rm -rf oraliq
python3 quvur_ishga.py
Chiqish (qaytish kodi 0, 3027 ms)
  [+] oqi          bajarildi      0.07s  
  [+] tekshir      bajarildi      0.12s  
  [+] tayyorla     bajarildi      0.15s  
  [+] oqit         bajarildi      1.46s  
  [+] baho         bajarildi      0.20s  

metrikalar: {   "f1": 0.8595,   "aniqlik": 0.8929,   "toliqlik": 0.8285 }

Keshlash: ikkinchi ishga tushirish

matn1 qator
python3 quvur_ishga.py
Chiqish (qaytish kodi 0, 822 ms)
  [=] oqi          keshdan            -  
  [=] tekshir      keshdan            -  
  [=] tayyorla     keshdan            -  
  [=] oqit         keshdan            -  
  [=] baho         keshdan            -  

metrikalar: {   "f1": 0.8595,   "aniqlik": 0.8929,   "toliqlik": 0.8285 }

Hamma bosqich keshdan bo'ldi — chunki chiqish fayllari kirishdan yangi. Vaqt deyarli nolga tushdi.

Endi faqat bitta bosqichning kirishini o'zgartiramiz:

matn2 qator
touch oraliq/xususiyat.parquet
python3 quvur_ishga.py
Chiqish (qaytish kodi 0, 2850 ms)
  [=] oqi          keshdan            -  
  [=] tekshir      keshdan            -  
  [=] tayyorla     keshdan            -  
  [+] oqit         bajarildi      1.61s  
  [+] baho         bajarildi      0.18s  

metrikalar: {   "f1": 0.8595,   "aniqlik": 0.8929,   "toliqlik": 0.8285 }

tayyorla ning chiqishi yangilandi, shuning uchun unga bog'liq oqit va baho qayta bajarildi. oqi va tekshir esa keshda qoldi — ular bu o'zgarishdan ta'sirlanmaydi.

Qanday qilib dastur buni bildi? U DAG ni o'qidi.

Chizmada tayyorla dan oqit ga strelka bor, oqit dan baho ga ham. Demak tayyorla o'zgarsa, o'sha strelkalar bo'ylab yuqoriga ta'sir tarqaladi. oqi va tekshir esa tayyorla dan oldin turibdi — ularga hech narsa yetib bormaydi.

Bu qaror odam tomonidan emas, graf tomonidan qabul qilindi. Siz "endi nimani qayta ishlatish kerak?" deb o'ylab o'tirmadingiz.

Mana orkestratsiyaning asosiy foydasi: faqat kerakli qism qayta ishlaydi, va nima kerakli ekanini tizim o'zi hisoblaydi.

Bu g'oya sizga tanish

Agar make yoki Makefile bilan ishlagan bo'lsangiz — bu aynan o'sha mexanizm. 1976-yilda yaratilgan.

Sabab bir xil: dasturchilar butun loyihani qayta kompilyatsiya qilishdan charchagan edi. Yechim ham bir xil: bog'liqlikni yozib qo'y, fayl vaqtiga qara, faqat kerakligini qayta qil.

Airflow, Dagster, Prefect — hammasi shu g'oyaning ML uchun moslashtirilgan ko'rinishi.

Xato bo'lganda nima bo'ladi

Bitta bosqichni ataylab buzamiz:

python16 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
from quvur.dag import Bosqich, ishga_tushir, chop_et

def yaxshi(): pass
def yomon(): raise ValueError('ma\'lumot manbasi javob bermadi')

b = [
    Bosqich('oqi', yaxshi),
    Bosqich('tekshir', yomon, bogliq=('oqi',)),
    Bosqich('tayyorla', yaxshi, bogliq=('tekshir',)),
    Bosqich('oqit', yaxshi, bogliq=('tayyorla',)),
    Bosqich('hisobot', yaxshi),
]
chop_et(ishga_tushir(b, holat_yoli='xato_holati.json'))
"
Chiqish (qaytish kodi 0, 46 ms)
  [+] hisobot      bajarildi          -  
  [+] oqi          bajarildi          -  
  [X] tekshir      yiqildi            -  ValueError: ma'lumot manbasi javob bermadi
  [-] tayyorla     o'tkazildi         -  bog'liq yiqildi: tekshir
  [-] oqit         o'tkazildi         -  bog'liq yiqildi: tayyorla

Uch narsaga e'tibor bering:

  1. tekshir yiqildi va sababi yozildi
  2. Unga bog'liq tayyorla va oqit o'tkazib yuborildi — bajarilmadi, chunki ma'nosiz
  3. hisobot esa bajarildi — u tekshir ga bog'liq emas

Bitta bosqich yiqilganda butun quvur to'xtamaydi — faqat unga bog'liq tarmoq to'xtaydi.

Sikl tekshiruvi

DAG da sikl bo'lmasligi kerak. Bo'lsa — darhol xato:

python11 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
from quvur.dag import Bosqich, tartibla

b = [Bosqich('a', lambda: None, bogliq=('b',)),
     Bosqich('b', lambda: None, bogliq=('a',))]
try:
    tartibla(b)
except ValueError as e:
    print('XATO (yaxshi):', e)
"
Chiqish (qaytish kodi 0, 38 ms)
XATO (yaxshi): sikl yoki yetishmayotgan bog'liqlik: ['a', 'b']

Holat fayli

Har ishga tushirish holatni yozadi — keyin uni kuzatuv tizimi o'qiydi (34-bob):

matn2 qator
python3 quvur_ishga.py > /dev/null 2>&1
cat quvur_holati.json
Chiqish (qaytish kodi 0, 1402 ms)
{
  "oqi": {
    "holat": "keshdan",
    "vaqt_s": 0.0,
    "xato": ""
  },
  "tekshir": {
    "holat": "keshdan",
    "vaqt_s": 0.0,
    "xato": ""
  },
  "tayyorla": {
    "holat": "keshdan",
    "vaqt_s": 0.0,
    "xato": ""
  },
  "oqit": {
    "holat": "keshdan",
    "vaqt_s": 0.0,
    "xato": ""
  },
  "baho": {
    "holat": "keshdan",
    "vaqt_s": 0.0,
    "xato": ""
  }
}

Airflow va muqobillari

Yuqoridagi 80 qator g'oyani ko'rsatadi. Ishlab chiqarishda tayyor tizimlar ishlatiladi:

python13 qator
import pandas as pd
show(pd.DataFrame([
    {"asbob": "Airflow", "kuchli tomoni": "Sanoat standarti, boy ekotizim, jadval bo'yicha ishga tushirish",
     "kamchiligi": "Og'ir: bazasi, rejalashtiruvchi, veb-server kerak"},
    {"asbob": "Prefect", "kuchli tomoni": "Python ga yaqin, sodda sintaksis",
     "kamchiligi": "Kichikroq hamjamiyat"},
    {"asbob": "Dagster", "kuchli tomoni": "Ma'lumot aktivlari (assets) markazda, turlar tekshiriladi",
     "kamchiligi": "Boshqacha fikrlash talab qiladi"},
    {"asbob": "Makefile", "kuchli tomoni": "Hech narsa o'rnatish shart emas, keshlash bepul",
     "kamchiligi": "Jadval, qayta urinish, kuzatuv yo'q"},
    {"asbob": "DVC pipelines", "kuchli tomoni": "5-bobdagi ma'lumot versiyalash bilan birlashgan",
     "kamchiligi": "Faqat ML quvurlari uchun"},
]))
asbobkuchli tomonikamchiligi
AirflowSanoat standarti, boy ekotizim, jadval bo'yicha ishga tushirishOg'ir: bazasi, rejalashtiruvchi, veb-server kerak
PrefectPython ga yaqin, sodda sintaksisKichikroq hamjamiyat
DagsterMa'lumot aktivlari (assets) markazda, turlar tekshiriladiBoshqacha fikrlash talab qiladi
MakefileHech narsa o'rnatish shart emas, keshlash bepulJadval, qayta urinish, kuzatuv yo'q
DVC pipelines5-bobdagi ma'lumot versiyalash bilan birlashganFaqat ML quvurlari uchun

Airflow da bizning quvur shunday ko'rinadi — tuzilma deyarli bir xil:

python22 qator
from airflow.decorators import dag, task
from datetime import datetime

@dag(schedule="0 3 * * *", start_date=datetime(2026, 9, 1), catchup=False)
def hisobot_quvuri():
    @task
    def oqi(): ...

    @task
    def tekshir(kirish): ...

    @task
    def tayyorla(kirish): ...

    @task
    def oqit(kirish): ...

    # bog'liqlik funksiya chaqiruvi orqali bildiriladi
    tayyorlangan = tayyorla(tekshir(oqi()))
    oqit(tayyorlangan)

hisobot_quvuri()

schedule="0 3 * * *" — har kuni soat 3:00 da. Bu bizning versiyamizda yo'q va Airflow ni tanlashning asosiy sabablaridan biri.

Kichik loyihaga Airflow kerak emas

Airflow ishga tushishi uchun PostgreSQL, rejalashtiruvchi va veb-server kerak — kamida uch konteyner, doimiy ishlab turadigan.

Bitta kunlik quvur uchun bu ortiqcha. Ketma-ketlik: skript → Makefile → DVC pipeline → Prefect/Dagster → Airflow. Har bosqichga o'tish uchun aniq sabab bo'lsin.

Idempotentlik: eng muhim xossa

Bosqich idempotent bo'lishi kerak: ikki marta bajarilsa, natija bir xil bo'lsin.

python11 qator
import pandas as pd
show(pd.DataFrame([
    {"yomon (idempotent emas)": "df.to_csv(yol, mode='a')  # qo'shib yozadi",
     "yaxshi (idempotent)": "df.to_csv(yol)  # ustidan yozadi"},
    {"yomon (idempotent emas)": "INSERT INTO t VALUES (...)",
     "yaxshi (idempotent)": "INSERT ... ON CONFLICT DO UPDATE"},
    {"yomon (idempotent emas)": "hisob += 1",
     "yaxshi (idempotent)": "hisob = manbadan_hisobla()"},
    {"yomon (idempotent emas)": "model_v{n+1}.joblib  # nomeri oshadi",
     "yaxshi (idempotent)": "model_{malumot_hash}.joblib"},
]))
yomon (idempotent emas)yaxshi (idempotent)
df.to_csv(yol, mode='a') # qo'shib yozadidf.to_csv(yol) # ustidan yozadi
INSERT INTO t VALUES (...)INSERT ... ON CONFLICT DO UPDATE
hisob += 1hisob = manbadan_hisobla()
model_v{n+1}.joblib # nomeri oshadimodel_{malumot_hash}.joblib

Nega bu muhim: qayta urinish (retry) idempotent bo'lmagan bosqichda ma'lumotni ikki marta qo'shib yuboradi. Va qayta urinish — orkestratsiyaning asosiy xususiyati.

Xulosa

Tushuncha Asosiy fikr
DAG Bosqichlar + bog'liqliklar; sikl bo'lmasligi kerak
Har bosqich faylga yozadi Shunda u mustaqil, keshlanadigan va qayta bajariladigan bo'ladi
Keshlash Chiqish kirishdan yangi bo'lsa, qayta bajarilmaydi
Qisman qayta ishlash Bitta bosqich o'zgarsa, faqat unga bog'liqlar qayta ishlaydi
Xato tarqalishi Yiqilgan bosqichga bog'liqlar o'tkazib yuboriladi, qolganlari ishlaydi
Holat fayli Har ishga tushirish natijasi yoziladi — kuzatuv uchun
Asbob tanlash Skript → Makefile → DVC → Prefect → Airflow; sababsiz o'tmang
Idempotentlik Ikki marta bajarilsa natija bir xil bo'lsin — retry shunga tayanadi

Talaba ishi

  1. dag.py ga qayta_urin=2 parametrini qo'shing: bosqich yiqilsa, ikki marta qayta urinsin. Qaysi xatolarda qayta urinish mantiqiy, qaysilarida yo'q?
  2. Bir-biriga bog'liq bo'lmagan bosqichlarni parallel bajaradigan variant yozing (ThreadPoolExecutor). Bizning quvurda qaysi bosqichlar parallel bo'la oladi?
  3. _yangimi faqat vaqtga qaraydi. Uni 5-bobdagi hash bilan almashtiring. Qaysi holatda bu muhim farq qiladi?
  4. quvur_ishga.py ga --faqat <bosqich> bayrog'ini qo'shing: faqat bitta bosqichni bajarsin.

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar