IlmHamroh
Data Science va sun'iy intellekt/MLOps va deploy11/14-dars44 daqiqa
Mundarija (23)

27.11-dars: Monitoring

27-QISM — MLOPS VA DEPLOY · 11-dars


1. Kirish va motivatsiya

Oldingi darsda 27.10-bob model API sini bulutga chiqardik: replikalar sonini hisobladik, avtomasshtablashni sozladik va yangi versiyani canary bilan chiqardik. Canary ning butun g'oyasi bitta narsaga tayanardi — metrikalar: xato ulushi, kechikish, yangi versiya va eski versiya taqqoslanishi. Bu darsda o'sha metrikalar qayerdan keladi, qanday yig'iladi va qachon odamni uyg'otishi kerakligini ko'ramiz.

Oddiy dasturiy ta'minotda monitoring asosan bitta savolga javob beradi: "tizim ishlayaptimi?". ML tizimida bu yetarli emas. Model ishlashi mumkin — 200 kodi qaytaradi, kechikish past, xatolar yo'q — lekin noto'g'ri ishlashi mumkin: kirish ma'lumotlari o'zgargan, belgilar quvuri buzilgan, bashoratlar siljigan. Va eng yomoni: to'g'ri javob (belgi) ko'pincha haftalar o'tib keladi. Mijoz ketdimi yoki yo'qmi — bir oydan keyin ma'lum bo'ladi; kredit qaytarildimi — yildan keyin.

Real vaziyat. Bank skoring modeli uch hafta davomida "sog'lom" ko'rindi: kechikish 40 ms, xatolar 0.1%, dashboard yashil. Keyin risk bo'limi kelishuvlar ulushi keskin oshganini payqadi. Sabab: yangi mobil ilova versiyasi daromadni ming so'mda yubora boshlagan edi, eski versiya — so'mda. Model uchun barcha mijozlar birdaniga "kam daromadli" bo'lib qoldi. Belgilar (kredit qaytarildimi) oylar keyin kelardi, lekin kirish taqsimoti va bashorat taqsimoti birinchi kuniyoq o'zgargan edi — faqat ularni hech kim kuzatmagan edi. 2-misol aynan shu holatni simulyatsiya qiladi.

Bu darsda ML tizimini to'rt qatlamda kuzatishni o'rganamiz: tizim, ma'lumot, model va biznes — va qachon ogohlantirish kerakligini hisoblaymiz.

Bu darsda:

  • Nimani kuzatish kerak: tizim, ma'lumot, model, biznes metrikalari
  • Belgilar kechikishi va proksi metrikalar
  • Metrikalar noldan: hisoblagich, gauge, gistogramma (Prometheus g'oyasi)
  • Persentilni gistogrammadan taxminlash va uning xatosi
  • Strukturali loglar va so'rov id
  • Ogohlantirish qoidalari: statik va statistik chegara, yolg'on signal va o'tkazib yuborish, "alert fatigue"
  • SLI, SLO va xato byudjeti, burn-rate ogohlantirishlari
  • Dashboard (Grafana) — nazariyada

ℹ Barcha misollar virtual vaqtda (daqiqa, kun — hisoblagich) va urug' bilan deterministik. Prometheus va Grafana ishga tushirilmaydi — ularning g'oyasi kichik Python kodi bilan noldan quriladi.


2. Nazariya — chuqur tushuntirish

2.1. Nimani kuzatish kerak: to'rt qatlam

text
QATLAM          SAVOL                          MISOL METRIKALAR
--------------  -----------------------------  ------------------------------------
1. TIZIM        xizmat ishlayaptimi, tezmi?    so'rovlar/s, xato ulushi (5xx),
                                               kechikish p50/p95/p99, CPU, xotira,
                                               navbat uzunligi, replikalar soni
2. MA'LUMOT     kirish kutilganidek mi?        null ulushi, sxema buzilishi (tur,
                                               oraliq), kategoriyalar ulushi,
                                               taqsimot siljishi 27.12-bob
3. MODEL        bashoratlar oqilonami?         bashorat taqsimoti (o'rtacha, ulush
                                               > chegara), ishonch, model versiyasi;
                                               belgilar kelganda - AUC, xato
4. BIZNES       foyda keltiryaptimi?           konversiya, kelishuvlar ulushi,
                                               firibgarlik zarari, shikoyatlar

TIZIM USULLARI:
  RED (so'rovga asoslangan xizmatlar): Rate, Errors, Duration
  USE (resurslar):  Utilization, Saturation, Errors

Birinchi qatlam har qanday xizmatda bor. ML ning o'ziga xosligi — 2 va 3-qatlamlar: tizim metrikalari mukammal bo'lsa ham model jim holda noto'g'ri ishlashi mumkin. To'rtinchi qatlam — oxirgi hakam: model metrikasi yaxshi, biznes yomon bo'lsa, noto'g'ri narsani optimallashtirayotgan bo'lishimiz mumkin.

"200 OK" — to'g'ri javob degani emas: ML xizmati tizim, ma'lumot, model va biznes qatlamlarida alohida kuzatiladi.

2.2. Belgilar kechikishi va proksi metrikalar

text
VAZIFA                 BELGI QACHON MA'LUM         KECHIKISH
mijoz ketishi          keyingi oyda                 ~ 30 kun
kredit defolti         to'lov muddatlari o'tgach     oylar - yil
firibgarlik            chargeback kelganda          kunlar - haftalar
tavsiya (bosish)       darhol                       soniyalar
talab prognozi         davr tugagach                kun - hafta

BELGI YO'Q PAYTDA NIMA KUZATILADI (PROKSI):
  kirish ma'lumotlari: null, oraliq, taqsimot
  bashorat taqsimoti: o'rtacha ball, "ijobiy" ulushi, ishonch
  qisman belgilar: erta signal (1-to'lov o'tkazib yuborildi)
  biznes proksi: tasdiqlangan arizalar ulushi

MUHIM:
  proksi signal "nimadir o'zgardi" deydi, "model yomonlashdi" demaydi
  -> tekshiruv boshlanadi; sifat esa belgilar kelganda tasdiqlanadi
  belgilar kelgach - sifat metrikasi QAYTA hisoblanadi (orqaga qarab)

Belgi kechiksa, birinchi signal — kirish va bashorat taqsimoti; sifat metrikasi keyin kelib, xulosani tasdiqlaydi yoki rad etadi.

2.3. Metrikalar: hisoblagich, gauge, gistogramma

text
COUNTER (hisoblagich): faqat o'sadi
  api_sorovlar_jami{yol="/predict", status="500"} 90
  foydali narsa - TEZLIK: rate = (qiymat2 - qiymat1) / vaqt
  qayta ishga tushishda 0 ga tushadi -> "reset" ni hisobga olish kerak

GAUGE: istalgan tomonga o'zgaradi
  api_navbat_uzunligi 3, xotira_bayt, yuklangan_model_versiya

HISTOGRAM: bucketlar (kumulyativ)
  api_kechikish_soniya_bucket{le="0.1"}  12815   <- 100 ms gacha
  api_kechikish_soniya_bucket{le="+Inf"} 20000   <- hammasi
  api_kechikish_soniya_sum / _count               <- o'rtacha uchun
  persentil TAXMINI: kerakli bucketni topib, ichida chiziqli interpolyatsiya

YORLIQLAR (labels): yol, status, model_versiya
  DIQQAT: foydalanuvchi id, so'rov id kabi yuqori kardinallikli yorliqlar
  YO'Q - har qiymat alohida vaqt qatori, xotira portlaydi
text
PROMETHEUS MODELI (pull):
  ilova /metrics sahifasida joriy qiymatlarni matn sifatida beradi
  Prometheus har 15-30 soniyada "tortib oladi" (scrape) va saqlaydi
  so'rov tili (PromQL) misollari:
    rate(api_sorovlar_jami[5m])                            so'rov/s
    sum(rate(api_sorovlar_jami{status=~"5.."}[5m]))
      / sum(rate(api_sorovlar_jami[5m]))                   xato ulushi
    histogram_quantile(0.99,
      sum by (le) (rate(api_kechikish_soniya_bucket[5m]))) p99
python
# Haqiqiy loyihada: prometheus_client (bu darsda o'rnatilmagan, g'oya uchun)
from prometheus_client import Counter, Histogram

SOROVLAR = Counter("api_sorovlar_jami", "Jami so'rovlar", ["yol", "status"])
KECHIKISH = Histogram("api_kechikish_soniya", "Kechikish",
                      buckets=[0.02, 0.05, 0.1, 0.2, 0.3, 0.5, 1.0])

with KECHIKISH.time():
    natija = model_bashorat(kirish)
SOROVLAR.labels(yol="/predict", status="200").inc()

Persentillarni o'rtachalamang — replikalar yoki vaqt oralig'i bo'yicha gistogramma bucketlari qo'shiladi, keyin persentil hisoblanadi.

2.4. Persentil taxmini xatosi

text
histogram_quantile bucket ICHIDAGI taqsimotni bilmaydi -> chiziqli taxmin
  xato <= bucket kengligi
  standart bucketlar (5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, ...)
    100-250 ms oralig'ida bitta bucket -> p99 = 320 ms bo'lsa, xato katta
  SLA chegarasi atrofida ZICH bucketlar kerak (masalan, 250, 300, 400 ms)

OXIRGI CHEKLI BUCKETDAN KATTA persentil:
  Prometheus oxirgi chekli chegarani qaytaradi -> p99 "1 s" bo'lib qotib qoladi
  -> bucketlar kutilgan maksimumdan yuqorigacha borsin

ALTERNATIVA: summary (klientda persentil) - lekin replikalar bo'yicha
  QO'SHIB BO'LMAYDI; shuning uchun odatda histogram afzal

Bucketlar SLA ga moslanadi: persentil aniqligi bucket kengligidan yaxshi bo'la olmaydi.

2.5. Strukturali loglar va so'rov id

text
ODDIY LOG:        "2026-09-24 10:15 bashorat 0.83 mijoz uchun, 41ms"
                   -> odam o'qiydi, mashina qiynaladi (regex, format o'zgaradi)

STRUKTURALI LOG:  {"sorov_id": "70fd086adf5b", "hodisa": "bashorat",
                   "model_versiya": "churn-v3", "ms": 31.5, "ball": 0.83}
                   -> filtr, guruhlash, metrikaga aylantirish oson

SO'ROV ID (correlation / trace id):
  gateway da yaratiladi -> har xizmatga sarlavhada uzatiladi
  -> har log qatorida bor -> bitta so'rovning butun izi bitta filtr bilan
  (to'liq variant: distributed tracing - OpenTelemetry, span lar)

QOIDALAR:
  har log qatorida: vaqt, daraja, xizmat, sorov_id, model_versiya
  shaxsiy ma'lumot (ism, telefon, karta) - NIQOBLANADI yoki yozilmaydi
  sirlar - hech qachon 27.9-bob
  bashorat loglari = kelajakdagi o'quv/baholash ma'lumoti:
    kirish + bashorat + versiya saqlanadi -> belgi kelganda birlashtiriladi

Bashorat logi — kelajakdagi baholash to'plami: kirish, bashorat va model versiyasi so'rov id bilan saqlanadi, belgi kelganda shu id orqali birlashtiriladi.

2.6. Ogohlantirish qoidalari

text
STATIK CHEGARA:      xato ulushi > 2%
  + sodda, tushunarli    - so'rovlar kam paytda (tun) shovqin katta
                         - mavsumiylikni bilmaydi

"FOR" SHARTI:        xato ulushi > 2% ketma-ket 15 daqiqa
  + bir martalik sakrashni e'tiborsiz qoldiradi
  - qisqa insidentni butunlay o'tkazib yuboradi, kechikish qo'shadi

STATISTIK (dinamik): x > o'rtacha(oxirgi kun) + k * std
  + moslashadi           - std ichida tun/kunduz aralash, insidentlar ham
                           asosga "singib" ketadi

SO'ROVLAR SONIGA MOS (binomial z):
  z = (xato - n * p) / sqrt(n * p * (1 - p)),   p - tarixiy ulush
  + tunda n kichik -> maxraj kichik -> bitta xato "yong'in" emas
  - n juda kichik bo'lsa normal yaqinlashuv buziladi -> minimal n yoki
    ketma-ketlik sharti

BAHOLASH (ma'lum insidentlar tarixida):
  precision = haqiqiy ogohlantirishlar / barcha ogohlantirishlar
  recall    = topilgan insidentlar / barcha insidentlar
  aniqlash kechikishi, haftasiga yolg'on ogohlantirishlar
text
ALERT FATIGUE:
  haftasiga 20 yolg'on signal -> odamlar ogohlantirishni o'qimay qo'yadi
  -> haqiqiy insident ham e'tiborsiz qoladi
QOIDA: har ogohlantirish = odam HOZIR nimadir qilishi kerak
  harakat talab qilmasa - dashboard yoki kunlik hisobot, ogohlantirish emas
  PAGE (tunda uyg'otadi) vs TICKET (ish vaqtida ko'riladi)

Ogohlantirish qoidasi ham model: uni tarixdagi ma'lum insidentlar ustida precision, recall va kechikish bilan baholang.

2.7. SLI, SLO va xato byudjeti

text
SLI (indicator):  o'lchanadigan ko'rsatkich
                  "yaxshi so'rovlar / barcha so'rovlar"
                  yaxshi = status < 500 VA kechikish < 300 ms
SLO (objective):  maqsad: 30 kunda SLI >= 99.7%
SLA (agreement):  mijoz bilan shartnoma (odatda SLO dan yumshoqroq, jarima bilan)

XATO BYUDJETI = 1 - SLO = 0.3% so'rovlar "yomon" bo'lishi MUMKIN
  30 kunda 10 mln so'rov -> 30 000 yomon so'rovga ruxsat
  to'liq uzilish ekvivalenti: 0.003 * 30 * 24 * 60 = 130 daqiqa

BYUDJET SIYOSATI:
  byudjet bor  -> yangi funksiyalar, tajribalar, tez deploy
  byudjet tugadi -> "muzlatish": faqat ishonchlilik ishlari

BURN RATE = (joriy yomon ulush) / (byudjet ulushi)
  burn = 1  -> byudjet aynan 30 kunda tugaydi
  burn = 14.4 -> 1 soatda oylik byudjetning 2% i (o'rtacha trafikda)

KO'P OYNALI OGOHLANTIRISH (Google SRE Workbook g'oyasi):
  page:   burn(1 soat) > 14.4  VA burn(5 daq)  > 14.4
  page:   burn(6 soat) > 6     VA burn(30 daq) > 6
  ticket: burn(3 kun)  > 1     VA burn(6 soat) > 1
  uzun oyna - ahamiyatlilik, qisqa oyna - "hali davom etyaptimi"

Xato byudjeti — ishonchlilik va tezlik orasidagi kelishuv; ogohlantirish yomon ulushga emas, byudjet yonish tezligiga qaratiladi.

2.8. Dashboard (Grafana) — nazariyada

text
BITTA XIZMAT UCHUN DASHBOARD (yuqoridan pastga - umumiydan tafsilotga):

+------------------------------------------------------------------+
| SLO: 99.72% (maqsad 99.7%)  | byudjet qoldi: 6%  | burn(1h): 0.4 |  <- holat
+------------------------------------------------------------------+
| so'rov/s (RED: Rate)        | xato ulushi (Errors) | p50/p95/p99 |  <- tizim
+------------------------------------------------------------------+
| null ulushi, sxema xatolari | kirish taqsimoti (asosiy belgilar) |  <- ma'lumot
+------------------------------------------------------------------+
| o'rtacha ball, ball > 0.5   | model versiyasi bo'yicha trafik    |  <- model
| AUC (belgilar kelgan kunlar, kechikish bilan belgilangan)        |
+------------------------------------------------------------------+
| biznes: konversiya, kelishuvlar ulushi                           |  <- biznes
+------------------------------------------------------------------+
| deploy belgilari (annotatsiya) - grafiklarda vertikal chiziq     |

QOIDALAR:
  har panelda: birlik, chegara chizig'i (SLO / ogohlantirish), vaqt oralig'i
  deploy va insidentlar annotatsiya bilan - "nima o'zgardi" darhol ko'rinadi
  dashboard - tahlil uchun; ogohlantirish - harakat uchun (2.6)

Dashboard savolga javob beradi, ogohlantirish harakatga chaqiradi — deploy annotatsiyalari "nima o'zgardi" savolini bir qarashda hal qiladi.

2.9. Tuzoqlar

Asosiy tuzoqlar: faqat tizim metrikalarini kuzatish ("200 OK" — model to'g'ri degani emas); belgilar kelishini kutib, proksi metrikalarsiz qolish; persentillarni o'rtachalash; SLA atrofida siyrak bucketlar; yuqori kardinallikli yorliqlar (foydalanuvchi id); counter reset ni hisobga olmaslik; matnli, tuzilmasiz loglar; so'rov id siz loglar; loglarda shaxsiy ma'lumot va sirlar; tungi kam trafikda statik chegara; harakat talab qilmaydigan ogohlantirishlar (alert fatigue); ogohlantirish qoidalarini tarixiy insidentlarda baholamaslik; fon xato darajasi byudjetning katta qismini yeydigan darajada qattiq SLO.


3. Tez ma'lumotnoma

python
import json
import math


def rate(oldingi, joriy, soniya):
    """Counter dan tezlik; reset bo'lsa joriy qiymat 0 dan boshlangan."""
    farq = joriy - oldingi if joriy >= oldingi else joriy
    return farq / soniya


def binomial_z(xato, n, p):
    return (xato - n * p) / math.sqrt(n * p * (1 - p))


def burn_rate(yomon, jami, slo):
    return (yomon / jami) / (1 - slo)


def log_json(**maydonlar):
    print(json.dumps(maydonlar, ensure_ascii=False))


log_json(sorov_id="70fd086adf5b", hodisa="bashorat", ms=31.5, ball=0.83)
yaml
# Prometheus ogohlantirish qoidasi (g'oya)
groups:
  - name: churn-api
    rules:
      - alert: TezByudjetYonishi
        expr: |
          (sum(rate(api_yomon_jami[1h])) / sum(rate(api_sorovlar_jami[1h]))) / 0.003 > 14.4
          and
          (sum(rate(api_yomon_jami[5m])) / sum(rate(api_sorovlar_jami[5m]))) / 0.003 > 14.4
        labels: {severity: page}
        annotations:
          summary: "churn-api: xato byudjeti tez yonmoqda"

Tuzilma xulosasi

text
to'rt qatlam: tizim (RED/USE) -> ma'lumot -> model -> biznes
belgi kechikadi -> proksi: null, sxema, bashorat taqsimoti
counter (rate, reset), gauge, histogram (bucketlar SLA atrofida zich)
persentil: bucketlarni qo'shib, keyin histogram_quantile
loglar: JSON, sorov_id, model_versiya, shaxsiy ma'lumotsiz
ogohlantirish: n ga mos (binomial z) + ketma-ketlik; tarixda baholash
SLO -> byudjet -> burn rate (1h/5m, 6h/30m, 3d/6h)

4. Batafsil misollar

Misollar real numpy/pandas/sklearn bilan (Python 3.14); vaqt — virtual (daqiqa va kun hisoblagichlari).

Misol 1 — Metrikalar noldan: counter, gauge, gistogramma va persentil

Uch metrika turi Prometheus g'oyasi bo'yicha kichik klasslar sifatida yoziladi va /metrics matn formatida chiqariladi. 20000 ta virtual kechikish (lognormal) ikki xil bucket to'plamida yig'iladi va histogram_quantile taxmini haqiqiy persentil bilan taqqoslanadi. Keyin counter dan tezlik hisoblanadi (pod qayta ishga tushishi bilan) va uch replikaning persentillari ikki usulda birlashtiriladi.

python
"""Metrikalar noldan: Counter, Gauge, Histogram (Prometheus g'oyasi),
persentilni bucketlardan taxminlash va uning xatosi. Virtual vaqt."""

import bisect
import math

import numpy as np


class Counter:
    """Faqat o'sadi. Tezlik (rate) - ikki o'lchov orasidagi farqdan."""

    def __init__(self, nom, yordam):
        self.nom, self.yordam, self.qiymatlar = nom, yordam, {}

    def inc(self, n=1.0, **yorliq):
        k = tuple(sorted(yorliq.items()))
        self.qiymatlar[k] = self.qiymatlar.get(k, 0.0) + n


class Gauge:
    """Istalgan tomonga o'zgaradi: navbat uzunligi, xotira, yuklangan model."""

    def __init__(self, nom, yordam):
        self.nom, self.yordam, self.qiymatlar = nom, yordam, {}

    def set(self, v, **yorliq):
        self.qiymatlar[tuple(sorted(yorliq.items()))] = v


class Histogram:
    """Kumulyativ bucketlar: le=chegara -> shu chegaragacha nechta."""

    def __init__(self, nom, yordam, chegaralar):
        self.nom, self.yordam = nom, yordam
        self.chegaralar = list(chegaralar) + [math.inf]
        self.sanoq = [0] * len(self.chegaralar)
        self.yigindi = 0.0
        self.soni = 0

    def observe(self, v):
        self.sanoq[bisect.bisect_left(self.chegaralar, v)] += 1
        self.yigindi += v
        self.soni += 1

    def kumulyativ(self):
        return np.cumsum(self.sanoq)

    def birlashtir(self, boshqa):
        assert self.chegaralar == boshqa.chegaralar, "bucketlar bir xil bo'lsin"
        h = Histogram(self.nom, self.yordam, self.chegaralar[:-1])
        h.sanoq = [a + b for a, b in zip(self.sanoq, boshqa.sanoq)]
        h.yigindi = self.yigindi + boshqa.yigindi
        h.soni = self.soni + boshqa.soni
        return h


def histogram_quantile(q, h):
    """Prometheus dagi kabi: bucket ichida chiziqli interpolyatsiya."""
    kum = h.kumulyativ()
    nishon = q * kum[-1]
    i = int(np.searchsorted(kum, nishon))
    if h.chegaralar[i] == math.inf:           # oxirgi chekli chegara qaytadi
        return h.chegaralar[i - 1]
    past = 0.0 if i == 0 else h.chegaralar[i - 1]
    oldingi = 0 if i == 0 else kum[i - 1]
    ichida = kum[i] - oldingi
    return past + (h.chegaralar[i] - past) * (nishon - oldingi) / ichida


def ekspozitsiya(metrikalar):
    """/metrics sahifasining matn formati (qisqartirilgan)."""
    qatorlar = []
    for m in metrikalar:
        tur = type(m).__name__.lower()
        qatorlar += [f"# HELP {m.nom} {m.yordam}", f"# TYPE {m.nom} {tur}"]
        if isinstance(m, Histogram):
            for le, s in zip(m.chegaralar, m.kumulyativ()):
                le_s = "+Inf" if le == math.inf else f"{le:g}"
                qatorlar.append(f'{m.nom}_bucket{{le="{le_s}"}} {s}')
            qatorlar.append(f"{m.nom}_sum {m.yigindi:.3f}")
            qatorlar.append(f"{m.nom}_count {m.soni}")
        else:
            for k in sorted(m.qiymatlar):
                y = ",".join(f'{a}="{b}"' for a, b in k)
                qatorlar.append(f"{m.nom}{{{y}}} {m.qiymatlar[k]:g}")
    return qatorlar


STANDART = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
MOSLANGAN = [0.02, 0.04, 0.06, 0.08, 0.1, 0.12, 0.15, 0.2, 0.25, 0.3, 0.4,
             0.5, 0.75, 1.0, 1.5, 2.5]


def main() -> None:
    rng = np.random.default_rng(0)

    print("=== 1. Yig'ish: 20000 so'rov (virtual kechikishlar) ===")
    sorovlar = Counter("api_sorovlar_jami", "Jami so'rovlar")
    navbat = Gauge("api_navbat_uzunligi", "Navbatdagi so'rovlar")
    kech = Histogram("api_kechikish_soniya", "So'rov kechikishi", STANDART)
    kechikishlar = rng.lognormal(math.log(0.08), 0.6, 20_000)
    for v in kechikishlar:
        kech.observe(float(v))
    for yol, ulush in [("/predict", 0.9), ("/health", 0.1)]:
        for status, p in [("200", 0.985), ("422", 0.01), ("500", 0.005)]:
            sorovlar.inc(round(20_000 * ulush * p), yol=yol, status=status)
    navbat.set(3)
    matn = ekspozitsiya([sorovlar, navbat, kech])
    tanlangan = [q for q in matn if "predict" in q or "navbat" in q
                 or q.startswith(("api_kechikish_soniya_sum",
                                  "api_kechikish_soniya_count"))
                 or 'le="0.1"' in q or 'le="+Inf"' in q]
    for q in tanlangan:
        print(f"  {q}")
    print(f"  (jami {len(matn)} qator, {len(matn) - len(tanlangan)} tasi "
          f"ko'rsatilmadi)")

    print("\n=== 2. Persentilni bucketlardan taxminlash ===")
    moslangan = Histogram("k2", "", MOSLANGAN)
    for v in kechikishlar:
        moslangan.observe(float(v))
    print(f"  {'q':>5} {'haqiqiy':>9} {'standart':>9} {'xato':>7} "
          f"{'moslangan':>9} {'xato':>7}")
    for q in [0.5, 0.9, 0.95, 0.99]:
        hq = float(np.quantile(kechikishlar, q))
        a = histogram_quantile(q, kech)
        b = histogram_quantile(q, moslangan)
        print(f"  {q:>5} {hq * 1000:>7.1f}ms {a * 1000:>7.1f}ms "
              f"{(a - hq) / hq:>+7.1%} {b * 1000:>7.1f}ms {(b - hq) / hq:>+7.1%}")
    print(f"  bucketlar: standart {len(STANDART)}, moslangan {len(MOSLANGAN)}"
          f" - aniqlik bucket kengligiga bog'liq")

    print("\n=== 3. Counter dan tezlik: 15 soniyalik o'lchovlar, qayta ishga "
          "tushish bilan ===")
    haqiqiy_tezlik = 40.0                       # so'rov/s
    olchovlar, qiymat = [], 0.0
    for i in range(12):
        if i == 7:
            qiymat = 0.0                        # pod qayta ishga tushdi
        qiymat += haqiqiy_tezlik * 15 + rng.normal(0, 20)
        olchovlar.append(qiymat)
    sodda, tuzatilgan = [], []
    for a, b in zip(olchovlar, olchovlar[1:]):
        sodda.append((b - a) / 15)
        tuzatilgan.append((b - a if b >= a else b) / 15)   # reset -> 0 dan
    print(f"  sodda farq:     min {min(sodda):>8.1f}, o'rtacha "
          f"{np.mean(sodda):>6.1f} so'rov/s")
    print(f"  reset hisobga:  min {min(tuzatilgan):>8.1f}, o'rtacha "
          f"{np.mean(tuzatilgan):>6.1f} so'rov/s (haqiqiy {haqiqiy_tezlik})")

    print("\n=== 4. Replikalar bo'yicha birlashtirish ===")
    replikalar = []
    for i, (med, sig, n) in enumerate([(0.06, 0.4, 9000), (0.065, 0.4, 9000),
                                       (0.3, 0.5, 2000)]):
        h = Histogram("r", "", MOSLANGAN)
        x = rng.lognormal(math.log(med), sig, n)
        for v in x:
            h.observe(float(v))
        replikalar.append((h, x))
        print(f"  replika {i}: {n} so'rov, p99 {histogram_quantile(0.99, h) * 1000:.0f} ms")
    hammasi = np.concatenate([x for _, x in replikalar])
    haqiqiy = float(np.quantile(hammasi, 0.99))
    ortacha_p99 = np.mean([histogram_quantile(0.99, h) for h, _ in replikalar])
    birlashgan = replikalar[0][0].birlashtir(replikalar[1][0]) \
        .birlashtir(replikalar[2][0])
    b99 = histogram_quantile(0.99, birlashgan)
    print(f"  haqiqiy umumiy p99:             {haqiqiy * 1000:>6.0f} ms")
    print(f"  p99 larning o'rtachasi (XATO):   {ortacha_p99 * 1000:>6.0f} ms "
          f"({(ortacha_p99 - haqiqiy) / haqiqiy:+.0%})")
    print(f"  birlashgan gistogramma p99:     {b99 * 1000:>6.0f} ms "
          f"({(b99 - haqiqiy) / haqiqiy:+.0%})")
    print("  ⭐ Persentillarni o'rtachalamang - gistogrammalarni qo'shing")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Yig'ish: 20000 so'rov (virtual kechikishlar) ===
  api_sorovlar_jami{status="200",yol="/predict"} 17730
  api_sorovlar_jami{status="422",yol="/predict"} 180
  api_sorovlar_jami{status="500",yol="/predict"} 90
  # HELP api_navbat_uzunligi Navbatdagi so'rovlar
  # TYPE api_navbat_uzunligi gauge
  api_navbat_uzunligi{} 3
  api_kechikish_soniya_bucket{le="0.1"} 12815
  api_kechikish_soniya_bucket{le="+Inf"} 20000
  api_kechikish_soniya_sum 1916.705
  api_kechikish_soniya_count 20000
  (jami 27 qator, 17 tasi ko'rsatilmadi)

=== 2. Persentilni bucketlardan taxminlash ===
      q   haqiqiy  standart    xato moslangan    xato
    0.5    79.7ms    83.5ms   +4.7%    79.8ms   +0.1%
    0.9   172.1ms   217.4ms  +26.3%   178.0ms   +3.5%
   0.95   213.1ms   240.1ms  +12.6%   217.9ms   +2.2%
   0.99   319.9ms   417.4ms  +30.5%   333.7ms   +4.3%
  bucketlar: standart 11, moslangan 16 - aniqlik bucket kengligiga bog'liq

=== 3. Counter dan tezlik: 15 soniyalik o'lchovlar, qayta ishga tushish bilan ===
  sodda farq:     min   -242.5, o'rtacha   14.5 so'rov/s
  reset hisobga:  min     38.2, o'rtacha   40.4 so'rov/s (haqiqiy 40.0)

=== 4. Replikalar bo'yicha birlashtirish ===
  replika 0: 9000 so'rov, p99 158 ms
  replika 1: 9000 so'rov, p99 180 ms
  replika 2: 2000 so'rov, p99 1075 ms
  haqiqiy umumiy p99:                569 ms
  p99 larning o'rtachasi (XATO):      471 ms (-17%)
  birlashgan gistogramma p99:        607 ms (+7%)
  ⭐ Persentillarni o'rtachalamang - gistogrammalarni qo'shing

Natija tahlili.

  • 1-bo'lim: /metrics sahifasi — oddiy matn. Counter yorliqlar bilan (yol, status), gistogramma kumulyativ bucketlar, _sum va _count bilan. le="0.1" qatori: 20000 so'rovdan 12815 tasi 100 ms gacha. Prometheus aynan shu matnni har 15-30 soniyada tortib oladi.
  • 2-bo'lim — persentil xatosi: standart bucketlar bilan p90 taxmini +26.3%, p99 — +30.5% xato (haqiqiy 319.9 ms o'rniga 417.4 ms). Sabab: 250-500 ms oralig'i bitta bucket va taxmin uning ichida chiziqli. Kechikish oralig'iga moslangan bucketlar bilan xato 0.1-4.3% ga tushdi. Xato har doim ijobiy chiqqani tasodif emas: lognormal dum bucket ichida chapga to'plangan, chiziqli interpolyatsiya esa massani tekis taqsimlaydi.
  • 3-bo'lim — counter reset: 8-o'lchovda pod qayta ishga tushdi va counter 0 dan boshlandi. Sodda farq manfiy tezlik (-242.5 so'rov/s) va o'rtacha 14.5 berdi — haqiqiy 40 o'rniga. Reset ni hisobga olgan rate 40.4 berdi. Prometheus ning rate() funksiyasi buni avtomatik qiladi, lekin o'zingiz hisoblasangiz — unutmang.
  • 4-bo'lim — birlashtirish: uchinchi replika sekin (p99 1075 ms), lekin trafikning faqat 10% ini oladi. Uch p99 ning o'rtachasi 471 ms — haqiqiy umumiy p99 (569 ms) dan 17% past, ya'ni muammoni yashiradi. Bucketlarni qo'shib hisoblangan p99 — 607 ms (+7%, bucket kengligi xatosi). Persentilni o'rtachalashda xatoning ishorasi va kattaligi taqsimotga bog'liq — shuning uchun bu usul umuman ishonchsiz.

Misol 2 — Strukturali loglar, so'rov id va belgilar kechikishi

Mijoz ketishi modeli 45 virtual kun ishlaydi, kuniga 400 bashorat. Har so'rov uch JSON hodisa yozadi (qabul, belgilar, bashorat) — hammasida bitta sorov_id. 25-kuni ma'lumot quvurida xato paydo bo'ladi: to'lovlarning 60% i tiyinda (x100) kela boshlaydi, muddat_oy esa 20% hollarda kelmaydi. Ketish belgisi 14 kundan keyin ma'lum (faraziy). Barcha metrikalar faqat loglardan hisoblanadi.

python
"""Strukturali loglar, so'rov id, ma'lumot/model metrikalari va belgilar
kechikishi. 45 virtual kun; 25-kuni ma'lumot quvurida xato paydo bo'ladi."""

import hashlib
import json
from collections import defaultdict

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score

KUNLAR = 45
KUNIGA = 400
BUZILISH_KUNI = 25
BELGI_KECHIKISHI = 14          # ketish belgisi 14 kundan keyin ma'lum (faraziy)
USTUNLAR = ["oylik_tolov", "muddat_oy", "murojaatlar"]


def mijozlar(rng, n):
    tolov = rng.lognormal(np.log(120_000), 0.4, n)          # so'm
    muddat = rng.integers(1, 60, n).astype(float)
    murojaat = rng.poisson(1.5, n).astype(float)
    logit = (-1.0 + 0.9 * np.log(tolov / 120_000) - 0.05 * muddat
             + 0.45 * murojaat)
    ketdi = rng.random(n) < 1 / (1 + np.exp(-logit))
    return np.column_stack([tolov, muddat, murojaat]), ketdi.astype(int)


def belgilar(X):
    """Modelga kirish: log(to'lov), muddat, murojaatlar; null -> median."""
    Z = X.copy()
    Z[:, 0] = np.log(np.where(np.isnan(Z[:, 0]), 120_000, Z[:, 0]))
    Z[:, 1] = np.where(np.isnan(Z[:, 1]), 30, Z[:, 1])
    return Z


def sorov_id(kun, i):
    return hashlib.sha256(f"{kun}-{i}".encode()).hexdigest()[:12]


def log(yozuvlar, **maydonlar):
    """Bitta hodisa = bitta JSON qator (vaqt belgisi o'rniga virtual kun)."""
    yozuvlar.append(json.dumps(maydonlar, ensure_ascii=False))


def main() -> None:
    rng = np.random.default_rng(3)
    Xo, yo = mijozlar(rng, 5000)
    model = LogisticRegression(max_iter=1000).fit(belgilar(Xo), yo)

    loglar, belgi_jadvali = [], {}
    for kun in range(KUNLAR):
        X, y = mijozlar(rng, KUNIGA)
        if kun >= BUZILISH_KUNI:
            # quvur xatosi: to'lovning bir qismi tiyinda (x100),
            # muddat esa ba'zan kelmaydi
            tiyin = rng.random(KUNIGA) < 0.6
            X[tiyin, 0] *= 100
            X[rng.random(KUNIGA) < 0.2, 1] = np.nan
        ball = model.predict_proba(belgilar(X))[:, 1]
        belgi_ms = rng.lognormal(np.log(8), 0.3, KUNIGA)
        kesh_otdi = rng.random(KUNIGA) < 0.03          # belgilar keshi o'tdi
        belgi_ms[kesh_otdi] += rng.uniform(120, 250, kesh_otdi.sum())
        model_ms = rng.lognormal(np.log(12), 0.3, KUNIGA)
        for i in range(KUNIGA):
            sid = sorov_id(kun, i)
            kirish = {u: (None if np.isnan(v) else round(float(v), 2))
                      for u, v in zip(USTUNLAR, X[i])}
            log(loglar, kun=kun, sorov_id=sid, hodisa="qabul", yol="/predict")
            log(loglar, kun=kun, sorov_id=sid, hodisa="belgilar",
                ms=round(float(belgi_ms[i]), 1), kesh=not kesh_otdi[i],
                kirish=kirish)
            log(loglar, kun=kun, sorov_id=sid, hodisa="bashorat",
                model_versiya="churn-v3", ms=round(float(model_ms[i]), 1),
                ball=round(float(ball[i]), 4))
            belgi_jadvali[sid] = (kun + BELGI_KECHIKISHI, int(y[i]))

    print("=== 1. Strukturali log qatorlari (JSON) ===")
    for q in loglar[:3]:
        print(f"  {q[:78]}".rstrip())
    print(f"  jami {len(loglar)} qator: har so'rovga 3 hodisa, bitta sorov_id")

    print("\n=== 2. So'rov id bo'yicha iz: eng sekin so'rov ===")
    yozuvlar = [json.loads(q) for q in loglar]
    izlar = defaultdict(list)
    for r in yozuvlar:
        izlar[r["sorov_id"]].append(r)
    jami_ms = {sid: sum(r.get("ms", 0) for r in rr) for sid, rr in izlar.items()}
    sekin = max(sorted(jami_ms), key=lambda s: jami_ms[s])
    for r in izlar[sekin]:
        qoshimcha = (f"{r['ms']:>6} ms" if "ms" in r else "")
        if r["hodisa"] == "belgilar":
            qoshimcha += f", kesh={r['kesh']}"
        qator = f"  {sekin} kun {r['kun']:>2} {r['hodisa']:<9} {qoshimcha}"
        print(qator.rstrip())
    otganlar = [r for r in yozuvlar if r["hodisa"] == "belgilar"]
    p99_kesh = np.percentile([r["ms"] for r in otganlar if r["kesh"]], 99)
    p99_yoq = np.percentile([r["ms"] for r in otganlar if not r["kesh"]], 99)
    print(f"  belgilar bosqichi p99: kesh bilan {p99_kesh:.0f} ms, "
          f"keshsiz {p99_yoq:.0f} ms -> sekinlik sababi topildi")

    print("\n=== 3. Loglardan kunlik metrikalar ===")
    kun_boyicha = defaultdict(list)
    for r in yozuvlar:
        if r["hodisa"] != "qabul":
            kun_boyicha[(r["kun"], r["hodisa"])].append(r)
    kunlik = {}
    for kun in range(KUNLAR):
        kir = [r["kirish"] for r in kun_boyicha[(kun, "belgilar")]]
        bash = kun_boyicha[(kun, "bashorat")]
        b = np.array([r["ball"] for r in bash])
        kunlik[kun] = {
            "null": np.mean([k["muddat_oy"] is None for k in kir]),
            "sxema": np.mean([(k["oylik_tolov"] or 0) > 2_000_000 for k in kir]),
            "ball": b.mean(), "yuqori": (b > 0.5).mean(),
            "sidlar": [r["sorov_id"] for r in bash], "ballar": b}
    bugun = KUNLAR - 1
    sarlavha = ["null muddat", "tolov>2mln", "o'rt ball", "ball>0.5",
                f"AUC ({bugun}-kun bilgani)"]
    print(f"  {'kun':>3} {sarlavha[0]:>11} {sarlavha[1]:>10} {sarlavha[2]:>9} "
          f"{sarlavha[3]:>8} {sarlavha[4]:>21}")
    for kun in [5, 20, 24, 25, 30, 31, 44]:
        m = kunlik[kun]
        if kun + BELGI_KECHIKISHI <= bugun:
            auc = roc_auc_score([belgi_jadvali[s][1] for s in m["sidlar"]],
                                m["ballar"])
            auc_s = f"{auc:.3f}"
        else:
            auc_s = "hali noma'lum"
        print(f"  {kun:>3} {m['null']:>11.1%} {m['sxema']:>10.1%} "
              f"{m['ball']:>9.3f} {m['yuqori']:>8.1%} {auc_s:>21}")

    print("\n=== 4. Qachon SEZILADI: proksi vs sifat metrikasi ===")
    ref = [kunlik[k]["ball"] for k in range(BUZILISH_KUNI)]
    ort_ref, std_ref = np.mean(ref), np.std(ref, ddof=1)
    birinchi = {}
    for kun in range(KUNLAR):
        m = kunlik[kun]
        if "null ulushi > 5%" not in birinchi and m["null"] > 0.05:
            birinchi["null ulushi > 5%"] = kun
        if "sxema buzilishi > 1%" not in birinchi and m["sxema"] > 0.01:
            birinchi["sxema buzilishi > 1%"] = kun
        if ("o'rtacha ball > 4 std" not in birinchi
                and abs(m["ball"] - ort_ref) > 4 * std_ref):
            birinchi["o'rtacha ball > 4 std"] = kun

    def oyna_auc(d):                            # [d-6, d] kunlar
        sid = [s for k in range(d - 6, d + 1) for s in kunlik[k]["sidlar"]]
        ball = np.concatenate([kunlik[k]["ballar"] for k in range(d - 6, d + 1)])
        return roc_auc_score([belgi_jadvali[s][1] for s in sid], ball)

    asos_auc = np.mean([oyna_auc(d) for d in range(6, BUZILISH_KUNI)])
    for d in range(6, bugun - BELGI_KECHIKISHI + 1):
        if oyna_auc(d) < asos_auc - 0.03:
            birinchi["7 kunlik AUC -0.03"] = d + BELGI_KECHIKISHI
            break
    print(f"  asos 7 kunlik AUC: {asos_auc:.3f}")
    for nom, kun in sorted(birinchi.items(), key=lambda x: x[1]):
        print(f"  {nom:<22} birinchi signal: {kun}-kun "
              f"(buzilishdan {kun - BUZILISH_KUNI} kun keyin)")
    print(f"  buzilish {BUZILISH_KUNI}-kuni; belgilar {BELGI_KECHIKISHI} kun "
          f"kechikadi")
    print("  ⭐ Belgilar kechiksa - ma'lumot va bashorat metrikalari (proksi) "
          "birinchi signal")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Strukturali log qatorlari (JSON) ===
  {"kun": 0, "sorov_id": "22841ea360fc", "hodisa": "qabul", "yol": "/predict"}
  {"kun": 0, "sorov_id": "22841ea360fc", "hodisa": "belgilar", "ms": 8.5, "kesh"
  {"kun": 0, "sorov_id": "22841ea360fc", "hodisa": "bashorat", "model_versiya":
  jami 54000 qator: har so'rovga 3 hodisa, bitta sorov_id

=== 2. So'rov id bo'yicha iz: eng sekin so'rov ===
  70fd086adf5b kun 32 qabul
  70fd086adf5b kun 32 belgilar   260.8 ms, kesh=False
  70fd086adf5b kun 32 bashorat    31.5 ms
  belgilar bosqichi p99: kesh bilan 16 ms, keshsiz 256 ms -> sekinlik sababi topildi

=== 3. Loglardan kunlik metrikalar ===
  kun null muddat tolov>2mln o'rt ball ball>0.5  AUC (44-kun bilgani)
    5        0.0%       0.0%     0.180     3.8%                 0.703
   20        0.0%       0.0%     0.178     2.8%                 0.763
   24        0.0%       0.0%     0.177     3.8%                 0.768
   25       19.0%      59.8%     0.581    60.0%                 0.537
   30       19.2%      63.0%     0.611    64.8%                 0.620
   31       22.2%      58.2%     0.569    58.5%         hali noma'lum
   44       15.8%      60.5%     0.585    61.5%         hali noma'lum

=== 4. Qachon SEZILADI: proksi vs sifat metrikasi ===
  asos 7 kunlik AUC: 0.768
  null ulushi > 5%       birinchi signal: 25-kun (buzilishdan 0 kun keyin)
  sxema buzilishi > 1%   birinchi signal: 25-kun (buzilishdan 0 kun keyin)
  o'rtacha ball > 4 std  birinchi signal: 25-kun (buzilishdan 0 kun keyin)
  7 kunlik AUC -0.03     birinchi signal: 39-kun (buzilishdan 14 kun keyin)
  buzilish 25-kuni; belgilar 14 kun kechikadi
  ⭐ Belgilar kechiksa - ma'lumot va bashorat metrikalari (proksi) birinchi signal

Natija tahlili.

  • 1-bo'lim: 18000 so'rov — 54000 JSON qator. Har qator mustaqil lug'at: filtrlash va guruhlash uchun regex kerak emas.
  • 2-bo'lim — so'rov id izi: eng sekin so'rov (32-kun) uchta yozuv bilan tiklandi: belgilar bosqichi 260.8 ms va kesh=False, model esa atigi 31.5 ms. Keyin barcha loglar bo'yicha: belgilar bosqichining p99 i keshli so'rovlarda 16 ms, keshsiz — 256 ms. Sekinlik sababi — model emas, belgilar keshining o'tkazib yuborilishi. So'rov id siz bu bog'lanishni topish ancha qiyin bo'lardi.
  • 3-bo'lim — kunlik metrikalar: 25-kunda hamma proksi metrikalar birdaniga o'zgardi: muddat_oy null ulushi 0% dan 19.0% ga, "to'lov > 2 mln" (sxema buzilishi) 59.8% ga, o'rtacha ball 0.177 dan 0.581 ga, 0.5 dan yuqori ballar 3.8% dan 60.0% ga. Lekin AUC ustuniga qarang: 44-kun holatida faqat 30-kungacha bo'lgan kunlarning AUC i ma'lum; 25-kun AUC i (0.537, odatiy ~0.77 o'rniga) aynan 39-kuni hisoblanishi mumkin bo'ldi.
  • 4-bo'lim — asosiy natija: uchta proksi signal buzilish kunining o'zida (0 kun kechikish) yondi, 7 kunlik AUC ning 0.03 ga tushishi esa 39-kuni — 14 kun keyin, ya'ni aynan belgi kechikishiga teng. Shu 14 kun davomida model har kuni yuzlab mijozga noto'g'ri ball berardi. Proksi metrikalar sababni ham ko'rsatadi (null va sxema — ma'lumot quvuri), AUC esa faqat "yomonlashdi" deydi.
  • Bog'lanish: bashorat loglari (kirish + ball + versiya + sorov_id) belgilar kelgach ular bilan birlashtirildi — xuddi shu mexanizm 27.13-darsda qayta o'qitish ma'lumoti manbai bo'ladi.

Misol 3 — Ogohlantirish qoidalari: precision, recall va alert fatigue

30 virtual kun, 5 daqiqalik oynalar (8640 ta). Trafik kunduzi ~2800, tunda ~110 so'rov/oyna; odatiy xato ulushi 0.5%. Tarixga kattaligi va davomiyligi turlicha 8 ta insident kiritilgan (haqiqat ma'lum). Yetti qoida taqqoslanadi: ikki statik chegara, "15 daqiqa davomida" sharti, ikki statistik chegara (oxirgi kun o'rtacha + k*std) va so'rovlar soniga moslangan binomial z (bir oynali va ikki oynali).

python
"""Ogohlantirish qoidalari: statik chegara, statistik chegara va binomial z.
30 virtual kun, 5 daqiqalik oynalar, 8 ta ma'lum insident. Precision, recall,
aniqlash kechikishi va haftasiga yolg'on ogohlantirishlar (alert fatigue)."""

import numpy as np
import pandas as pd

OYNA_DAQ = 5
KUNGA = 24 * 60 // OYNA_DAQ          # 288 oyna
KUNLAR = 30
P0 = 0.005                           # odatiy xato ulushi

# (kun, soat, davomiylik daqiqa, qo'shimcha xato ulushi) - "haqiqat"
INSIDENTLAR = [(2, 14, 30, 0.05), (5, 3, 60, 0.03), (8, 10, 15, 0.015),
               (12, 20, 120, 0.008), (16, 12, 10, 0.10), (20, 4, 45, 0.02),
               (24, 15, 240, 0.005), (28, 9, 20, 0.04)]


def malumot(rng):
    t = np.arange(KUNLAR * KUNGA)
    soat = (t % KUNGA) * OYNA_DAQ / 60
    # tunda ~100, kunduzi ~3000 so'rov / 5 daqiqa
    kutilgan = 100 + 2900 * np.exp(-((soat - 14) / 4.5) ** 2)
    n = rng.poisson(kutilgan)
    p = np.full(len(t), P0)
    haqiqat = np.zeros(len(t), dtype=int)          # insident raqami (1..8)
    for i, (kun, s, davom, qosh) in enumerate(INSIDENTLAR, start=1):
        bosh = kun * KUNGA + s * 60 // OYNA_DAQ
        oxir = bosh + davom // OYNA_DAQ
        p[bosh:oxir] += qosh
        haqiqat[bosh:oxir] = i
    xato = rng.binomial(n, p)
    return pd.DataFrame({"n": n, "xato": xato, "insident": haqiqat})


def qoidalar(df):
    r = df["xato"] / df["n"].clip(lower=1)
    oldingi = r.shift(1)
    ort = oldingi.rolling(KUNGA, min_periods=KUNGA // 2).mean()
    std = oldingi.rolling(KUNGA, min_periods=KUNGA // 2).std()
    # kunlik tarixiy ulush (xatolar / so'rovlar), joriy oynasiz
    p_hat = (df["xato"].shift(1).rolling(KUNGA, min_periods=KUNGA // 2).sum()
             / df["n"].shift(1).rolling(KUNGA, min_periods=KUNGA // 2).sum())
    z = (df["xato"] - df["n"] * p_hat) / np.sqrt(df["n"] * p_hat * (1 - p_hat))

    def ketma_ket(shart, k):
        return shart.astype(int).rolling(k).sum() == k

    return {
        "statik r > 2%": r > 0.02,
        "statik r > 5%": r > 0.05,
        "statik r > 2%, 15 daq": ketma_ket(r > 0.02, 3),
        "o'rtacha + 3 std": r > ort + 3 * std,
        "o'rtacha + 5 std": r > ort + 5 * std,
        "binomial z > 4": z > 4,
        "binomial z > 4, 10 daq": ketma_ket(z > 4, 2),
    }


def baholash(signal, insident):
    """Ketma-ket yongan oynalar -> bitta ogohlantirish (hodisa)."""
    s = signal.fillna(False).to_numpy()
    boshlar = np.flatnonzero(s & ~np.r_[False, s[:-1]])
    haqiqiy_alert, topilgan, kechikish = 0, {}, []
    ins = insident.to_numpy()
    for b in boshlar:
        oxir = b
        while oxir + 1 < len(s) and s[oxir + 1]:
            oxir += 1
        tegishli = set(ins[b:oxir + 1]) - {0}
        if tegishli:
            haqiqiy_alert += 1
            for i in tegishli:
                topilgan.setdefault(i, b)
    for i, b in topilgan.items():
        ins_bosh = int(np.flatnonzero(ins == i)[0])
        kechikish.append((b - ins_bosh) * OYNA_DAQ)
    jami = len(boshlar)
    yolgon = jami - haqiqiy_alert
    return {"jami": jami, "yolgon_haftasiga": yolgon / (KUNLAR / 7),
            "precision": haqiqiy_alert / jami if jami else float("nan"),
            "recall": len(topilgan) / len(INSIDENTLAR),
            "kechikish": float(np.median(kechikish)) if kechikish
            else float("nan"), "topilgan": sorted(topilgan)}


def main() -> None:
    rng = np.random.default_rng(11)
    df = malumot(rng)

    print("=== 1. Ma'lumot: 30 kun x 288 oyna ===")
    r = df["xato"] / df["n"]
    kun = np.arange(len(df)) % KUNGA
    tun = (kun < 5 * 12) & (df["insident"] == 0)
    kunduz = (kun >= 12 * 12) & (kun < 16 * 12) & (df["insident"] == 0)
    print(f"  oynalar: {len(df)}, insidentli oynalar: "
          f"{(df['insident'] > 0).sum()}")
    print(f"  tungi oyna: o'rtacha {df['n'][tun].mean():.0f} so'rov, xato "
          f"ulushi std {r[tun].std():.4f}")
    print(f"  kunduzgi oyna: o'rtacha {df['n'][kunduz].mean():.0f} so'rov, "
          f"xato ulushi std {r[kunduz].std():.4f}")
    print("  bir xil P0 = 0.5%, lekin tunda shovqin ancha katta")

    print("\n=== 2. Qoidalar taqqoslash ===")
    ustun = "yolg'on/hafta"
    print(f"  {'qoida':<24} {'alert':>5} {ustun:>13} "
          f"{'precision':>9} {'recall':>6} {'kechikish':>9}")
    natija = {}
    for nom, signal in qoidalar(df).items():
        b = baholash(signal, df["insident"])
        natija[nom] = b
        kech = (f"{b['kechikish']:.0f} daq" if b["kechikish"] == b["kechikish"]
                else "-")
        print(f"  {nom:<24} {b['jami']:>5} {b['yolgon_haftasiga']:>13.1f} "
              f"{b['precision']:>9.2f} {b['recall']:>6.2f} {kech:>9}")

    print("\n=== 3. Qaysi insidentlar topildi ===")
    for nom in ["statik r > 5%", "statik r > 2%, 15 daq", "binomial z > 4, 10 daq"]:
        belgilar = "".join("X" if i in natija[nom]["topilgan"] else "-"
                           for i in range(1, len(INSIDENTLAR) + 1))
        print(f"  {nom:<24} {belgilar}  (1-8)")
    tavsif = [f"{i}: +{q:.1%} {d} daq" for i, (_, _, d, q) in
              enumerate(INSIDENTLAR, start=1)]
    print("  " + ", ".join(tavsif[:4]))
    print("  " + ", ".join(tavsif[4:]))

    print("\n=== 4. Qaror: yolg'on <= 1/hafta, eng yuqori recall ===")
    mos = {k: v for k, v in natija.items() if v["yolgon_haftasiga"] <= 1}
    if mos:
        eng = max(mos, key=lambda k: (mos[k]["recall"], -mos[k]["kechikish"]))
        print(f"  mos: {len(mos)} ta; tanlandi: {eng} (recall "
              f"{mos[eng]['recall']:.2f}, kechikish "
              f"{mos[eng]['kechikish']:.0f} daq)")
    shovqinli = max(natija, key=lambda k: natija[k]["yolgon_haftasiga"])
    print(f"  eng shovqinli: {shovqinli} - haftasiga "
          f"{natija[shovqinli]['yolgon_haftasiga']:.1f} yolg'on signal")
    print("  ⭐ Chegara so'rovlar soniga moslashsin - tungi shovqin 'yong'in' emas")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot: 30 kun x 288 oyna ===
  oynalar: 8640, insidentli oynalar: 108
  tungi oyna: o'rtacha 110 so'rov, xato ulushi std 0.0069
  kunduzgi oyna: o'rtacha 2819 so'rov, xato ulushi std 0.0013
  bir xil P0 = 0.5%, lekin tunda shovqin ancha katta

=== 2. Qoidalar taqqoslash ===
  qoida                    alert yolg'on/hafta precision recall kechikish
  statik r > 2%               94          19.6      0.11   0.88     0 daq
  statik r > 5%                4           0.0      1.00   0.38     0 daq
  statik r > 2%, 15 daq        6           0.0      1.00   0.62    10 daq
  o'rtacha + 3 std           126          26.6      0.10   0.88     0 daq
  o'rtacha + 5 std            31           4.7      0.35   0.75     0 daq
  binomial z > 4              33           3.0      0.61   1.00     0 daq
  binomial z > 4, 10 daq      10           0.0      1.00   0.88     5 daq

=== 3. Qaysi insidentlar topildi ===
  statik r > 5%            XX--X---  (1-8)
  statik r > 2%, 15 daq    XXX--X-X  (1-8)
  binomial z > 4, 10 daq   XXXXXX-X  (1-8)
  1: +5.0% 30 daq, 2: +3.0% 60 daq, 3: +1.5% 15 daq, 4: +0.8% 120 daq
  5: +10.0% 10 daq, 6: +2.0% 45 daq, 7: +0.5% 240 daq, 8: +4.0% 20 daq

=== 4. Qaror: yolg'on <= 1/hafta, eng yuqori recall ===
  mos: 3 ta; tanlandi: binomial z > 4, 10 daq (recall 0.88, kechikish 5 daq)
  eng shovqinli: o'rtacha + 3 std - haftasiga 26.6 yolg'on signal
  ⭐ Chegara so'rovlar soniga moslashsin - tungi shovqin 'yong'in' emas

Natija tahlili.

  • 1-bo'lim — muammoning ildizi: xato ulushi bir xil (0.5%), lekin tunda 110 so'rovlik oynada uning std si 0.0069, kunduzi 2819 so'rovda — 0.0013, besh barobar katta. Tunda 3 ta xato — 2.7% "xato ulushi". Har qanday qoida bu shovqinni hisobga olishi kerak.
  • 2-bo'lim, statik chegaralar: r > 2% 8 insidentdan 7 tasini topdi (recall 0.88), lekin haftasiga 19.6 yolg'on signal berdi (precision 0.11) — deyarli hammasi tunda. r > 5% da yolg'on yo'q, lekin recall 0.38. "15 daqiqa" sharti yolg'onni nolga tushirdi, lekin recall 0.62 va mediana kechikish 10 daqiqa.
  • Statistik chegaralar eng yomon chiqdi (kutilgandan farqli, lekin tushunarli): o'rtacha + 3 std haftasiga 26.6 yolg'on signal berdi. Sabab: kunlik std tun va kunduzni aralashtiradi, tungi oynalar esa o'rtacha + 3 std ni tez-tez oshadi. "Aqlli" dinamik chegara so'rovlar sonini bilmasa, statikdan yaxshi emas.
  • Binomial z: bir oynali z > 4 hamma 8 insidentni topdi (recall 1.00), lekin haftasiga 3.0 yolg'on — tunda n juda kichik (kutilgan xato ~0.55) va normal yaqinlashuv buziladi. Ikki ketma-ket oyna sharti bilan yolg'on 0.0 ga tushdi, recall 0.88, mediana kechikish 5 daqiqa.
  • 3-bo'lim: tanlangan qoida faqat 7-insidentni (+0.5%, 4 soat) o'tkazib yubordi — bu fon xato ulushining o'zi kattaligidagi siljish. Uni 5 daqiqalik oynada ajratish qiyin; uni sekin yonish ogohlantirishi ushlashi kerak (4-misol). "15 daqiqa" qoidasi esa 10 daqiqalik +10% insidentni (5) o'tkazib yubordi — for: sharti insidentdan uzun bo'lsa, uni hech qachon ko'rmaydi.
  • 4-bo'lim — qaror: "haftasiga ko'pi bilan 1 yolg'on" talabiga uchta qoida mos keldi; recall bo'yicha eng yaxshisi — binomial z > 4, 10 daq.

Misol 4 — SLO, xato byudjeti va burn-rate ogohlantirishlari

30 virtual kun, 5 daqiqalik oynalar. "Yomon" so'rov — 5xx yoki 300 ms dan sekin; odatiy yomon ulush 0.1%. SLO — 99.7%. Besh insident: katta xato (1 soat, +20%), tungi xato (3 soat, +3%), yarim uzilish (20 daqiqa, +50%), sekin regressiya (3 kun, +0.6%) va qisqa xato (30 daqiqa, +10%). Uch burn-rate qoidasi (2.7-bo'lim) sodda "5 daqiqada yomon ulush > byudjet" qoidasi bilan taqqoslanadi.

python
"""SLO/SLI, xato byudjeti va ko'p oynali burn-rate ogohlantirishlari.
30 virtual kun, 5 daqiqalik oynalar, 5 ta ma'lum insident."""

import numpy as np
import pandas as pd

OYNA = 5                       # daqiqa
KUNGA = 288
KUNLAR = 30
SLO = 0.997                    # 30 kunda so'rovlarning 99.7% "yaxshi"
BYUDJET = 1 - SLO
ASOS_YOMON = 0.001             # odatiy yomon ulush (5xx yoki > 300 ms)

# (nom, kun, soat, davomiylik daqiqa, qo'shimcha yomon ulush)
INSIDENTLAR = [("A katta xato", 3, 10, 60, 0.20),
               ("B tungi xato", 9, 2, 180, 0.03),
               ("C yarim uzilish", 14, 14, 20, 0.50),
               ("D sekin regressiya", 20, 0, 3 * 24 * 60, 0.006),
               ("E qisqa xato", 27, 13, 30, 0.10)]

# Google SRE Workbook g'oyasi: (nom, uzun oyna, qisqa oyna, burn chegarasi)
QOIDALAR = [("page: tez yonish", 12, 1, 14.4),       # 1 soat / 5 daq
            ("page: sekin yonish", 72, 6, 6.0),      # 6 soat / 30 daq
            ("ticket", 864, 72, 1.0)]                # 3 kun / 6 soat


def malumot(rng):
    t = np.arange(KUNLAR * KUNGA)
    soat = (t % KUNGA) * OYNA / 60
    n = rng.poisson(200 + 2800 * np.exp(-((soat - 14) / 4.5) ** 2))
    p = np.full(len(t), ASOS_YOMON)
    ins = np.full(len(t), -1)
    for i, (_, kun, s, davom, q) in enumerate(INSIDENTLAR):
        b = kun * KUNGA + s * 60 // OYNA
        p[b:b + davom // OYNA] += q
        ins[b:b + davom // OYNA] = i
    return pd.DataFrame({"n": n, "yomon": rng.binomial(n, p), "ins": ins})


def burn(df, oyna):
    y = df["yomon"].rolling(oyna, min_periods=1).sum()
    n = df["n"].rolling(oyna, min_periods=1).sum()
    return (y / n) / BYUDJET


def main() -> None:
    rng = np.random.default_rng(5)
    df = malumot(rng)

    print("=== 1. SLI va xato byudjeti (30 kun) ===")
    jami, yomon = df["n"].sum(), df["yomon"].sum()
    sli = 1 - yomon / jami
    byudjet = BYUDJET * jami
    print(f"  so'rovlar: {jami}, yomon: {yomon}")
    print(f"  SLI = {sli:.4%}, SLO = {SLO:.1%} -> "
          f"{'bajarildi' if sli >= SLO else 'BUZILDI'}")
    print(f"  byudjet: {byudjet:.0f} yomon so'rov, sarflandi "
          f"{yomon / byudjet:.1%}")
    print(f"  ruxsat etilgan to'liq uzilish: 30 kunda "
          f"{BYUDJET * 30 * 24 * 60:.0f} daqiqa")

    print("\n=== 2. Byudjet sarfi vaqt bo'yicha ===")
    kunlik = df.groupby(np.arange(len(df)) // KUNGA)[["n", "yomon"]].sum()
    kum = kunlik["yomon"].cumsum() / byudjet
    muzlatish = next((k for k in kum.index if kum[k] >= 1.0), None)
    for kun in [2, 3, 8, 9, 13, 14, 19, 22, 26, 27, 29]:
        belgi = " <- byudjet tugadi" if muzlatish == kun else ""
        print(f"  {kun:>2}-kun oxiri: {kum[kun]:>7.1%}{belgi}")
    if muzlatish is not None:
        print(f"  siyosat: {muzlatish}-kundan yangi funksiyalar chiqarish "
              f"to'xtatiladi, faqat ishonchlilik ishlari")
    else:
        qoldi = 1 - kum.iloc[-1]
        print(f"  byudjet tugamadi, qoldi {qoldi:.1%}")
        for i, (nom, *_) in enumerate(INSIDENTLAR):
            m = df["ins"] == i
            ulush = (df.loc[m, "yomon"].sum()
                     - ASOS_YOMON * df.loc[m, "n"].sum()) / byudjet
            if ulush > qoldi:
                print(f"  yana bitta '{nom}' ({ulush:.1%}) SLO ni buzardi")

    print("\n=== 3. Insidentlar: byudjetning qancha qismi ===")
    for i, (nom, _, _, davom, q) in enumerate(INSIDENTLAR):
        m = df["ins"] == i
        qosh = df.loc[m, "yomon"].sum() - ASOS_YOMON * df.loc[m, "n"].sum()
        print(f"  {nom:<19} {davom:>5} daq, +{q:>5.1%}: byudjetning "
              f"{qosh / byudjet:>6.1%} qismi")

    print("\n=== 4. Burn-rate ogohlantirishlari ===")
    signallar = {}
    for nom, uzun, qisqa, chegara in QOIDALAR:
        signallar[nom] = (burn(df, uzun) > chegara) & (burn(df, qisqa) > chegara)
    signallar[f"sodda: 5 daq > {BYUDJET:.1%}"] = df["yomon"] / df["n"] > BYUDJET
    ustun = "yolg'on"
    print(f"  {'qoida':<22} {'alertlar':>8} {ustun:>7}  "
          f"birinchi signal (daq, insident boshidan)")
    for nom, s in signallar.items():
        s = s.to_numpy()
        boshlar = np.flatnonzero(s & ~np.r_[False, s[:-1]])
        yaqin = []
        for b in boshlar:        # insident ichida yoki tugaganidan 6 soat ichida
            oyna = df["ins"].to_numpy()[max(0, b - 72):b + 1]
            yaqin.append(oyna.max() >= 0)
        yolgon = len(boshlar) - sum(yaqin)
        kech = []
        for i, (qisqa_nom, kun, soat, davom, _) in enumerate(INSIDENTLAR):
            b0 = kun * KUNGA + soat * 60 // OYNA
            ichida = boshlar[(boshlar >= b0) & (boshlar < b0 + davom // OYNA)]
            kech.append(f"{qisqa_nom[0]}:{(ichida[0] - b0) * OYNA}"
                        if len(ichida) else f"{qisqa_nom[0]}:-")
        print(f"  {nom:<22} {len(boshlar):>8} {yolgon:>7}  {' '.join(kech)}")
    print("\n=== 5. SLO tanlash: bir xil tizim, turli maqsad ===")
    for slo in [0.995, 0.997, 0.999]:
        b = 1 - slo
        sarf = yomon / (b * jami)
        holat = "bajarildi" if sli >= slo else "BUZILDI"
        print(f"  SLO {slo:.1%}: odatiy fon byudjetning "
              f"{ASOS_YOMON / b:>4.0%} ini yeydi, jami sarf {sarf:>6.1%} "
              f"-> {holat}")
    print("  ⭐ Byudjet - ishonchlilik va tezlik orasidagi kelishuv; "
          "ogohlantirish - byudjet yonish tezligiga")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. SLI va xato byudjeti (30 kun) ===
  so'rovlar: 9762495, yomon: 27471
  SLI = 99.7186%, SLO = 99.7% -> bajarildi
  byudjet: 29287 yomon so'rov, sarflandi 93.8%
  ruxsat etilgan to'liq uzilish: 30 kunda 130 daqiqa

=== 2. Byudjet sarfi vaqt bo'yicha ===
   2-kun oxiri:    3.3%
   3-kun oxiri:   18.4%
   8-kun oxiri:   23.9%
   9-kun oxiri:   25.8%
  13-kun oxiri:   30.0%
  14-kun oxiri:   51.5%
  19-kun oxiri:   57.1%
  22-kun oxiri:   80.3%
  26-kun oxiri:   84.7%
  27-kun oxiri:   91.5%
  29-kun oxiri:   93.8%
  byudjet tugamadi, qoldi 6.2%
  yana bitta 'A katta xato' (14.0%) SLO ni buzardi
  yana bitta 'C yarim uzilish' (20.4%) SLO ni buzardi
  yana bitta 'D sekin regressiya' (19.8%) SLO ni buzardi

=== 3. Insidentlar: byudjetning qancha qismi ===
  A katta xato           60 daq, +20.0%: byudjetning  14.0% qismi
  B tungi xato          180 daq, + 3.0%: byudjetning   0.8% qismi
  C yarim uzilish        20 daq, +50.0%: byudjetning  20.4% qismi
  D sekin regressiya   4320 daq, + 0.6%: byudjetning  19.8% qismi
  E qisqa xato           30 daq, +10.0%: byudjetning   5.8% qismi

=== 4. Burn-rate ogohlantirishlari ===
  qoida                  alertlar yolg'on  birinchi signal (daq, insident boshidan)
  page: tez yonish              3       0  A:10 B:- C:5 D:- E:25
  page: sekin yonish            2       0  A:10 B:- C:5 D:- E:-
  ticket                        3       0  A:30 B:- C:5 D:1285 E:-
  sodda: 5 daq > 0.3%         664     559  A:0 B:- C:0 D:5 E:0

=== 5. SLO tanlash: bir xil tizim, turli maqsad ===
  SLO 99.5%: odatiy fon byudjetning  20% ini yeydi, jami sarf  56.3% -> bajarildi
  SLO 99.7%: odatiy fon byudjetning  33% ini yeydi, jami sarf  93.8% -> bajarildi
  SLO 99.9%: odatiy fon byudjetning 100% ini yeydi, jami sarf 281.4% -> BUZILDI
  ⭐ Byudjet - ishonchlilik va tezlik orasidagi kelishuv; ogohlantirish - byudjet yonish tezligiga

Natija tahlili.

  • 1-bo'lim: SLI 99.7186% — SLO (99.7%) bajarildi, lekin byudjetning 93.8% i sarflandi. 99.7% SLO 30 kunda atigi 130 daqiqalik to'liq uzilishga teng.
  • 2-bo'lim — byudjet vaqt bo'yicha: sarf zinapoya shaklida — har insident bir pog'ona: 3-kun (18.4%), 14-kun (51.5%), 20-22-kunlar (sekin regressiya, 80.3% gacha). Oy oxirida 6.2% qoldi: A, C yoki D kabi yana bitta hodisa SLO ni buzardi. Byudjet siyosati bo'yicha bu holatda jamoa xavfli deploy larni kechiktirishi kerak.
  • 3-bo'lim — kichik lekin uzoq ham qimmat: 20 daqiqalik yarim uzilish (C) byudjetning 20.4% ini yedi, 3 kunlik +0.6% regressiya (D) esa 19.8% ini — deyarli bir xil. Tungi uch soatlik +3% (B) atigi 0.8% — tunda trafik kam. Byudjet foydalanuvchilar sonini sanaydi, daqiqalarni emas.
  • 4-bo'lim — ogohlantirishlar: burn-rate qoidalarida yolg'on signal yo'q. Tez yonish page i A ni 10, C ni 5, E ni 25 daqiqada topdi; D (sekin regressiya) ni faqat ticket qoidasi topdi — 1285 daqiqada (taxminan 21 soat), bu to'g'ri: sekin yonish tunda odamni uyg'otishga arzimaydi, lekin ish vaqtida ko'rilishi kerak. B umuman ogohlantirish bermadi — u byudjetning 0.8% ini yedi, ya'ni harakat talab qilmaydi. Sodda qoida esa 664 ta ogohlantirish berdi, 559 tasi yolg'on — bu alert fatigue ning aniq ko'rinishi.
  • Nozik joy (halol): E (30 daqiqa, +10%) byudjetning 5.8% ini yedi, lekin tez yonish page i uni faqat 25-daqiqada ushladi. Sabab: burn rate — ulush, kunduzgi cho'qqi soatida esa trafik o'rtachadan ko'p. "14.4 = 1 soatda 2%" formulasi o'rtacha trafik uchun; cho'qqida byudjet tezroq yonadi.
  • 5-bo'lim — SLO tanlash: 99.9% SLO da odatiy fon (0.1% yomon) byudjetning 100% ini yeydi — insidentlarsiz ham SLO chegarada, amalda buzildi (sarf 281.4%). SLO tizimning haqiqiy fon darajasidan kelib chiqib tanlanadi: fon byudjetning kichik qismini (masalan, 20-35%) olishi kerak.

5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Xatolar va kechikish yaxshi — model yaxshi" Ma'lumot va bashorat taqsimoti alohida kuzatiladi
"Sifatni belgilar kelganda ko'ramiz" Belgi haftalar kechikadi; proksi — birinchi signal
"p99 larni o'rtachalash mumkin" Bucketlarni qo'shib, keyin persentil
"Standart bucketlar yetarli" SLA atrofida zich bucketlar kerak
"Dinamik chegara har doim aqlliroq" So'rovlar sonini bilmasa, statikdan yomon
"Ko'proq ogohlantirish — xavfsizroq" Alert fatigue: haqiqiysi ham e'tiborsiz qoladi
"for: 15m yolg'on signalni yo'qotadi — yaxshi" Qisqa insidentni butunlay o'tkazib yuboradi
"SLO qancha yuqori bo'lsa, shuncha yaxshi" Fon darajasi byudjetni yeb qo'yadi
"Har yomon daqiqa bir xil muhim" Byudjet foydalanuvchilarni sanaydi: cho'qqi qimmat
"Loglar — faqat xatoni tuzatish uchun" Bashorat logi — kelajakdagi baholash to'plami

6. Keng tarqalgan xatolar va yechimlari

1. Faqat tizim metrikalari

text
dashboard: so'rov/s, xato %, p99, CPU                      # ⚠️
+ null ulushi, sxema xatolari, o'rtacha ball, ball > 0.5,  # ✅
  model_versiya bo'yicha trafik, biznes metrikasi

2. Persentillarni o'rtachalash

python
p99 = np.mean([replika.p99 for replika in replikalar])      # ⚠️
p99 = histogram_quantile(0.99, sum(bucketlar))              # ✅

3. Yuqori kardinallikli yorliq

python
SOROVLAR.labels(foydalanuvchi=uid).inc()                    # ⚠️ millionlab qator
SOROVLAR.labels(yol="/predict", status="200").inc()         # ✅ id - logda

4. Tuzilmasiz log

python
print(f"bashorat {ball} mijoz {mijoz_ismi} uchun")          # ⚠️ matn + shaxsiy
log_json(sorov_id=sid, hodisa="bashorat", ball=ball,        # ✅
         model_versiya="churn-v3")

5. Tungi shovqinga statik chegara

yaml
expr: xato_ulushi > 0.02                                    # ⚠️ tunda 3 xato = 2.7%
expr: binomial_z > 4 (ketma-ket 2 oyna, n >= minimal)       # ✅

6. Byudjetni emas, ulushni ogohlantirish

yaml
expr: yomon_ulush_5m > 0.003                                # ⚠️ yuzlab alert
expr: burn_1h > 14.4 and burn_5m > 14.4                     # ✅ page

7. Counter reset

python
tezlik = (joriy - oldingi) / soniya                         # ⚠️ manfiy tezlik
tezlik = rate(oldingi, joriy, soniya)                       # ✅ reset hisobga

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 11-qism (o'tilgan): gipoteza testlari — binomial z va ko'p marta tekshirish muammosi ogohlantirishlarda ham bor
  • 18-qism (o'tilgan): precision/recall — ogohlantirish qoidasini baholash xuddi klassifikatorni baholashdek
  • 27.8-dars (o'tilgan): API xatolarini to'g'ri status kodlari bilan qaytarish — aks holda xato metrikasi yolg'on
  • 27.10-dars (o'tilgan): canary va avtomasshtablash shu metrikalar asosida qaror qiladi
  • 27.12-dars: drift aniqlash — 2-misoldagi "kirish taqsimoti o'zgardi" signalini statistik testlar bilan (PSI, KS) aniqlash
  • 27.13-dars: qayta o'qitish — bashorat loglari va kechikkan belgilar qayta o'qitish ma'lumotiga aylanadi

8. Eng yaxshi amaliyotlar

  1. To'rt qatlam: tizim, ma'lumot, model, biznes.

  2. Belgi kechiksa — kirish va bashorat taqsimoti proksi sifatida.

  3. Histogram bucketlari SLA atrofida zich; persentil — bucketlar yig'indisidan.

  4. Yorliqlar past kardinallikli; id lar — logda.

  5. Loglar JSON, sorov_id va model_versiya bilan, shaxsiy ma'lumotsiz.

  6. Ogohlantirish = harakat; qolgani dashboard yoki ticket.

  7. Qoidalar so'rovlar soniga mos (binomial) va tarixiy insidentlarda baholangan.

  8. SLO fon darajasiga mos; ogohlantirish — burn rate bo'yicha, ko'p oynali.

  9. Dashboard da deploy annotatsiyalari.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

text
1.  ML monitoringining to'rt qatlami qaysilar?
2.  Belgilar kechikishi nima va unga qarshi nima qilinadi?
3.  Counter va gauge farqi?
4.  Nima uchun persentillarni o'rtachalab bo'lmaydi?
5.  histogram_quantile xatosi nimaga bog'liq?
6.  Nima uchun foydalanuvchi id yorliq bo'lmasligi kerak?
7.  So'rov id nima beradi?
8.  Tunda statik chegara nima uchun ko'p yolg'on signal beradi?
9.  "for: 15m" ning kamchiligi?
10. Xato byudjeti qanday hisoblanadi?
11. Burn rate = 1 nimani bildiradi?
12. Nima uchun ikki oyna (uzun va qisqa) ishlatiladi?
Javoblar
  1. Tizim, ma'lumot, model, biznes
  2. To'g'ri javob haftalar keyin keladi; kirish va bashorat taqsimoti proksi sifatida kuzatiladi
  3. Counter faqat o'sadi (tezlik hisoblanadi), gauge istalgan tomonga
  4. Persentil chiziqli emas; trafik ulushlari va taqsimotlar farq qiladi
  5. Bucket kengligiga
  6. Har qiymat — alohida vaqt qatori, xotira portlaydi
  7. Bitta so'rovning barcha xizmatlardagi izi
  8. n kichik — ulushning tasodifiy tebranishi katta
  9. Kechikish qo'shadi, qisqa insidentni o'tkazib yuboradi
  10. (1 - SLO) * jami so'rovlar
  11. Byudjet aynan davr oxirida tugaydi
  12. Uzun — ahamiyatlilik, qisqa — hali davom etayotgani (tez tiklanish)

Vazifa 2: Xatolarni tuzating

text
1. p99 = (replika1_p99 + replika2_p99 + replika3_p99) / 3
2. KECHIKISH = Histogram(..., buckets=[0.005, 0.01, 0.1, 1, 10])   # SLA 300 ms
3. SOROVLAR.labels(mijoz_id=mijoz.id, yol=yol).inc()
4. log.info(f"Mijoz {ism} ({telefon}) uchun ball {ball}")
5. alert: xato_ulushi_5m > 0.01   (tunda 100 so'rov/oyna)
6. SLO = 99.99%, fon xato ulushi 0.05%
Javoblar
  1. Replikalar bucketlarini qo'shing, keyin histogram_quantile.
  2. SLA atrofida zich: [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 1, 2.5].
  3. mijoz_id ni yorliqdan olib tashlang — u logga (JSON) yoziladi.
  4. log_json(sorov_id=sid, hodisa="bashorat", ball=ball) — ism va telefon yozilmaydi.
  5. Binomial z yoki minimal so'rovlar soni + ketma-ket oynalar sharti.
  6. Byudjet 0.01%, fon 0.05% — fon byudjetdan 5 barobar katta: SLO insidentlarsiz ham buziladi. SLO ni fon darajasiga moslang (masalan, 99.8%).

Vazifa 3: Gistogramma

1-misoldagi Histogram ga birlashtir dan tashqari farq(boshqa) metodini qo'shing: ikki o'lchov orasidagi bucket farqlari (vaqt oynasi bo'yicha gistogramma — Prometheus dagi rate(..._bucket[5m]) g'oyasi). 10 ta 1 daqiqalik o'lchovdan oxirgi 5 daqiqaning p99 ini hisoblang va butun davr p99 i bilan taqqoslang.

Vazifa 4: Proksi metrikalar

2-misolga ikki proksi qo'shing: (a) 0.5 dan yuqori ballar ulushining kunlik z-skori (tarixiy o'rtacha va std bo'yicha); (b) murojaatlar ning kunlik o'rtachasi. Buzilishni boshqa turga almashtiring: murojaatlar hisoblagichi ikki barobar ko'p sanay boshlaydi (quvur xatosi). Qaysi proksi uni birinchi ko'radi, AUC qachon tushadi?

Vazifa 5: Ogohlantirishni sozlash

3-misolda binomial z > k, m oyna qoidasi uchun k (3, 4, 5) va m (1, 2, 3) to'rini ko'rib chiqing. Har kombinatsiya uchun recall, yolg'on/hafta va kechikishni jadvalga yozing. "Haftasiga <= 1 yolg'on" talabi bilan eng yaxshi kombinatsiyani tanlang. Keyin boshqa urug' bilan yangi 30 kunlik ma'lumot yaratib, tanlangan qoidani qayta baholang — natija saqlanadimi (ortiqcha moslash tekshiruvi)?

Vazifa 6: Xato byudjeti

4-misolga "haftalik hisobot" funksiyasini qo'shing: har hafta oxirida SLI, sarflangan byudjet, eng qimmat insident va "shu tezlikda byudjet qachon tugaydi" prognozi (oxirgi 7 kunlik burn rate bo'yicha). Hisobotni jadval sifatida chop eting.

Vazifa 7: O'ylash

Kredit skoring modeli uchun monitoring loyihalashtirishingiz kerak. Defolt belgisi 6-12 oydan keyin ma'lum bo'ladi. Rahbariyat "oyiga bir marta AUC hisobot qilamiz, shu yetarli" demoqda. Nima deysiz va qanday monitoring taklif qilasiz?

Javob

Qisqa javob: yetarli emas. Oyiga bir marta hisoblangan AUC 6-12 oy oldingi arizalar haqida gapiradi — muammo bugun boshlansa, u hisobotda yarim yildan keyin ko'rinadi. Belgi kechikishi uzun bo'lgani uchun asosiy og'irlik proksi metrikalarga tushadi.

1. Qatlamlar va chastota

Qatlam Metrika Chastota Ogohlantirish
Tizim xato ulushi, p99, SLO burn daqiqa page / ticket
Ma'lumot null, sxema, asosiy belgilar taqsimoti (daromad birligi!) soat / kun ticket
Model ball taqsimoti, tasdiqlash ulushi, versiya bo'yicha kun ticket
Erta belgi 1-to'lov o'tkazib yuborildi (30 kun) hafta hisobot + ticket
Yakuniy sifat AUC, kalibrlash (6-12 oy keyin) oy hisobot
Biznes tasdiqlangan kreditlar hajmi, zarar hafta/oy hisobot

2. Erta belgi — eng qimmatli proksi

"Birinchi to'lov o'tkazib yuborildi" (first payment default) 30-60 kunda ma'lum va yakuniy defolt bilan kuchli bog'langan. Uning AUC ini haftalik hisoblash kechikishni 6-12 oydan 1-2 oyga qisqartiradi.

3. Ma'lumot tekshiruvlari — birinchi kundan

Darsning boshidagi "ming so'm va so'm" holati sxema va oraliq tekshiruvi bilan birinchi soatda ushlanardi: daromad taqsimotining medianasi 1000 barobar o'zgargan. Bu 2-misoldagi tolov>2mln ustuniga o'xshash oddiy qoida.

4. Model versiyasi va segmentlar

Ball taqsimoti umumiy emas, segmentlar bo'yicha (kanal: mobil/veb, ilova versiyasi, hudud) — o'zgarish ko'pincha bitta segmentda boshlanadi.

5. Ogohlantirish intizomi

Tunda uyg'otadigan (page) faqat tizim qatlami; ma'lumot va model — ish vaqtidagi ticket; har qoida tarixiy ma'lumotda (3-misol kabi) baholanadi.

Xulosa: oylik AUC — yakuniy tasdiq, monitoring emas. Monitoring — kirish va bashoratlarni har kuni kuzatish, erta belgilar va tez sxema tekshiruvlari; AUC esa ular ko'rsatgan signalni oylar o'tib tasdiqlaydi.

Nimani mustahkamlaydi: 2.2, 2.3, 2.6, 2.7-bo'limlar.


Xulosa

Bu darsda ML tizimini ishlab chiqarishda kuzatishni ko'rdik.

Eng muhim uch fikr:

  1. Belgi kechikadi — proksi metrikalar birinchi. 2-misolda ma'lumot quvuridagi xato null ulushi, sxema buzilishi va bashorat taqsimotida buzilish kunining o'zida ko'rindi, 7 kunlik AUC esa 14 kundan keyin tushdi — aynan belgi kechikishiga teng. Strukturali loglar va sorov_id bu metrikalarni ham, eng sekin so'rov sababini ham (belgilar keshi) loglardan hisoblash imkonini berdi.

  2. Metrikani to'g'ri yig'ish — o'zi alohida vazifa. 1-misolda standart bucketlar p99 ni 30.5% ga oshirib ko'rsatdi, moslangan bucketlar — 4.3% ga; replikalar p99 larini o'rtachalash sekin replikani yashirib, haqiqiy p99 ni 17% ga kamaytirdi; counter reset esa manfiy tezlik berdi.

  3. Ogohlantirish — harakat, byudjet — kelishuv. 3-misolda statik va "o'rtacha + k*std" chegaralari tungi shovqin tufayli haftasiga 19.6 va 26.6 yolg'on signal berdi; so'rovlar soniga moslangan binomial z va ikki oynali shart yolg'onsiz 8 insidentdan 7 tasini topdi. 4-misolda burn-rate qoidalari yolg'onsiz ishladi, sodda ulush qoidasi esa 664 ta ogohlantirish berdi; 99.9% SLO esa fon xatolar tufayli insidentlarsiz ham bajarib bo'lmas bo'lib chiqdi.

Keyingi darsda Drift aniqlash: 2-misoldagi "kirish taqsimoti o'zgardi" signalini qanday qilib statistik jihatdan asosli aniqlash — PSI, KS testi, kategoriyalar uchun chi-kvadrat, ko'p o'lchovli drift va yolg'on signallar murosasi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
27.11-dars: Monitoring — IlmHamroh