Real vaqtda ML: oqimga model qo'shish
Javob kelganda kech bo'lsa
27-bobdagi servis so'rov kutadi: kimdir /bashorat ga murojaat qiladi, javob oladi.
Bu ko'p holatda to'g'ri. Lekin hamma holatda emas.
NOVA VIVO ni eslang. Ballon noto'g'ri omborga keldi. Kim so'raydi? Hech kim. Skaner hodisa yuboradi va o'z ishini davom ettiradi. Agar siz javobni so'rov kutib o'tirsangiz, uni hech qachon olmaysiz.
Bunday tizimda model so'ramaydi — tinglaydi.
10-bobdagi daftarni eslang: hodisalar oqimga yoziladi, iste'molchilar o'qiydi. Model ham shunday iste'molchi bo'lishi mumkin.
Ikki xil "real vaqt"
Bu ibora ikki boshqa narsani anglatadi va ularni chalkashtirish keng tarqalgan.
So'rov-javob (online). Foydalanuvchi kutib turibdi. 27-bobdagi servis. Muhimi — kechikish: p99 100 ms dan oshmasin.
Oqimli (streaming). Hech kim kutmaydi. Hodisa keladi, model qaror qiladi, natija boshqa oqimga yoziladi. Muhimi — to'xtamaslik: hodisa yo'qolmasin, navbat o'smasin.
import pandas as pd show(pd.DataFrame([ {"": "Kim boshlaydi", "So'rov-javob": "Foydalanuvchi", "Oqimli": "Hodisa"}, {"": "Kim kutadi", "So'rov-javob": "Foydalanuvchi", "Oqimli": "Hech kim"}, {"": "Asosiy o'lchov", "So'rov-javob": "p99 kechikish", "Oqimli": "Orqada qolish (lag)"}, {"": "Yuk oshsa", "So'rov-javob": "429 qaytaring (30-bob)", "Oqimli": "Navbat o'sadi, kutadi"}, {"": "Misol", "So'rov-javob": "RostUy filtri", "Oqimli": "NOVA VIVO ballon hodisasi"}, ]))
| So'rov-javob | Oqimli | |
|---|---|---|
| Kim boshlaydi | Foydalanuvchi | Hodisa |
| Kim kutadi | Foydalanuvchi | Hech kim |
| Asosiy o'lchov | p99 kechikish | Orqada qolish (lag) |
| Yuk oshsa | 429 qaytaring (30-bob) | Navbat o'sadi, kutadi |
| Misol | RostUy filtri | NOVA VIVO ballon hodisasi |
To'rtinchi qatorga e'tibor bering. Oqimli tizimda rad etish yo'q — hodisa Kafka da turadi va kutadi. Bu yaxshi (yo'qolmaydi) va yomon (orqada qolish o'sadi).
Brokerni ko'taramiz
docker rm -f kitob-kafka > /dev/null 2>&1 docker run -d --name kitob-kafka -p 19092:9092 \ -e KAFKA_NODE_ID=1 -e KAFKA_PROCESS_ROLES=broker,controller \ -e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \ -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:19092 \ -e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \ -e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \ -e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \ -e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \ -e KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1 \ -e KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1 \ -e KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS=0 \ apache/kafka:3.9.0 > /dev/null for i in $(seq 1 60); do python3 -c "from kafka import KafkaAdminClient; KafkaAdminClient(bootstrap_servers='localhost:19092')" > /dev/null 2>&1 \ && { echo "kafka ${i} soniyada tayyor"; break; } sleep 1 done python3 -c " import sys, pathlib; sys.path.insert(0, 'src') if not pathlib.Path('model.onnx').exists(): import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from quvur.xususiyat import XUSUSIYATLAR, tayyorla from quvur.format import onnx_ga d = pd.read_csv('../datasets/ijara.csv') x, y = tayyorla(d), d['broker'] Xtr, _, ytr, _ = train_test_split(x, y, test_size=0.25, stratify=y, random_state=42) m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3, random_state=42, n_jobs=1).fit(Xtr, ytr) onnx_ga(m, len(XUSUSIYATLAR), 'model.onnx') print('model tayyor') "
kafka 1 soniyada tayyor model tayyor
Oqimli bashoratchi
Kod sodda: Kafka dan o'qi, bashorat qil, Kafka ga yoz.
shu faylni yaratingsrc/xizmat/oqimli.py
"""Oqimli bashorat: Kafka -> model -> Kafka. 3-bobdagi qoida bu yerda ham amal qiladi: xususiyat kodi O'SHA `tayyorla()`. Oqimli tizimda alohida kod yozish - skewning eng keng tarqalgan sababi. """ import json import time from typing import Any import pandas as pd from quvur.format import onnx_ehtimol, onnx_sessiya from quvur.xususiyat import XOM_USTUNLAR, tayyorla def toplamni_bahola(sessiya, xabarlar: list[dict[str, Any]], chegara: float) -> list[dict[str, Any]]: """To'plamni birgalikda baholaydi (29-bob: to'plam arzonroq).""" d = pd.DataFrame([x["elon"] for x in xabarlar], columns=XOM_USTUNLAR) e = onnx_ehtimol(sessiya, tayyorla(d).values) natija = [] for xabar, ehtimol in zip(xabarlar, e): natija.append({ "elon_id": xabar["elon_id"], "ehtimol": round(float(ehtimol), 4), "broker": bool(ehtimol >= chegara), "model_versiya": "v1", "vaqt": time.time(), }) return natija
Oqimli tizim — skew uchun eng xavfli joy
3-bobdagi tikuvchi va fabrikani eslang: ikki xil santimetr, kostyum tor.
Oqimli tizimda bu xato ayniqsa oson sodir bo'ladi. Sabab: oqimli kod ko'pincha boshqa jamoada, boshqa tilda (Java, Scala) yoziladi, va u xususiyatlarni qaytadan hisoblaydi.
O'qitish pandas bilan, oqim Java bilan. Ikkalasi ham "to'g'ri" yozilgan. Natija — model
bema'ni javob beradi va sabab topilmaydi.
Shuning uchun yuqoridagi kod tayyorla() ni import qiladi, qayta yozmaydi. Agar oqim
boshqa tilda bo'lsa, yagona yechim — xususiyat hisobini alohida servisga chiqarish va
ikkala tomon o'shani chaqirsin.
Hodisalarni yuboramiz
python3 -c " import json import pandas as pd from kafka import KafkaProducer from kafka.admin import KafkaAdminClient, NewTopic a = KafkaAdminClient(bootstrap_servers='localhost:19092') mavjud = a.list_topics() for t in ('elon-kirish', 'elon-natija'): if t not in mavjud: a.create_topics([NewTopic(name=t, num_partitions=3, replication_factor=1)]) XUS = ['xonalar','maydon','qavat','jami_qavat','narx_mln','tavsif_uzunligi', 'rasmlar_soni','telefon_takrorlanishi','kunlik_elonlar','egasi_tasdiqlangan'] d = pd.read_csv('../datasets/ijara.csv').drop_duplicates('elon_id').head(2000) p = KafkaProducer(bootstrap_servers='localhost:19092', value_serializer=lambda v: json.dumps(v).encode(), key_serializer=lambda k: k.encode(), linger_ms=20, batch_size=65536) for r in d.itertuples(): p.send('elon-kirish', key=r.elon_id, value={'elon_id': r.elon_id, 'elon': {u: getattr(r, u) for u in XUS}}) p.flush() print(f'{len(d):,} elon oqimga yuborildi') " 2>&1 | grep -v Warning
2,000 elon oqimga yuborildi
Bashoratchini ishga tushiramiz
python3 -c " import sys, json, time; sys.path.insert(0, 'src') from kafka import KafkaConsumer, KafkaProducer from quvur.format import onnx_sessiya from xizmat.oqimli import toplamni_bahola s = onnx_sessiya('model.onnx') c = KafkaConsumer('elon-kirish', bootstrap_servers='localhost:19092', auto_offset_reset='earliest', group_id='bashoratchi', enable_auto_commit=False, consumer_timeout_ms=8000, max_poll_records=200) p = KafkaProducer(bootstrap_servers='localhost:19092', value_serializer=lambda v: json.dumps(v).encode(), key_serializer=lambda k: k.encode(), linger_ms=20, batch_size=65536) jami = broker = 0 toplam = [] t0 = time.perf_counter() def jonat(toplam): global jami, broker if not toplam: return for n in toplamni_bahola(s, toplam, 0.5): p.send('elon-natija', key=n['elon_id'], value=n) broker += n['broker'] jami += len(toplam) for m in c: toplam.append(json.loads(m.value)) if len(toplam) >= 200: jonat(toplam); toplam = [] jonat(toplam) p.flush() c.commit() c.close() dt = time.perf_counter() - t0 print(f'qayta ishlandi : {jami:,} elon') print(f'broker dedi : {broker:,} ({broker/max(jami,1)*100:.1f}%)') print(f'vaqt : {dt:.2f} s -> {jami/dt:,.0f} elon/s') " 2>&1 | grep -v Warning
[0;93m2026-09-29 21:25:15.745283767 [W:onnxruntime:Default, device_discovery.cc:164 DiscoverDevicesForPlatform] GPU device discovery failed: device_discovery.cc:89 ReadFileContents Failed to open file: "/sys/class/drm/card0/device/vendor"[m qayta ishlandi : 2,000 elon broker dedi : 671 (33.6%) vaqt : 9.25 s -> 216 elon/s
Diqqat: to'plam bilan ishladik — 200 tadan. 29-bobda o'lchagandik: to'plam bitta-bitta ishlashdan o'nlab barobar arzon.
Oqimli tizimda bu bepul keladi: foydalanuvchi kutmayapti, demak to'plam yig'ishdan kechikish zarari yo'q.
Natijani o'qiymiz
python3 -c " import json, collections from kafka import KafkaConsumer c = KafkaConsumer('elon-natija', bootstrap_servers='localhost:19092', auto_offset_reset='earliest', group_id='hisobot', consumer_timeout_ms=8000) n = collections.Counter() namuna = None for m in c: v = json.loads(m.value) n['broker' if v['broker'] else 'ega'] += 1 if namuna is None and v['broker']: namuna = v c.close() print('natija oqimida:') for k, v in sorted(n.items()): print(f' {k:8s}: {v:>6,}') print() print('namuna xabar:', json.dumps(namuna, ensure_ascii=False)) " 2>&1 | grep -v Warning
natija oqimida:
broker : 671
ega : 1,329
namuna xabar: {"elon_id": "E100997", "ehtimol": 0.8716, "broker": true, "model_versiya": "v1", "vaqt": 1790699117.0010428}
Har natija elon_id kaliti bilan yozildi — 10-bobdagi qoida: bir e'lonning barcha
natijalari bitta partitionda, tartibda.
model_versiya maydoniga ham e'tibor bering. Keyinroq "bu qaror qaysi model chiqargan?"
degan savol albatta tug'iladi — va u javobsiz qolmasin.
Orqada qolish: oqimning asosiy o'lchovi
So'rov-javobda p99 ni o'lchaganmiz (29-bob). Oqimda boshqa raqam muhim: lag — iste'molchi oqimdan qancha orqada.
python3 -c " from kafka import KafkaConsumer, TopicPartition c = KafkaConsumer(bootstrap_servers='localhost:19092', group_id='bashoratchi') tps = [TopicPartition('elon-kirish', p) for p in c.partitions_for_topic('elon-kirish')] c.assign(tps) oxiri = c.end_offsets(tps) jami_lag = 0 print(f'{\"partition\":>10s} {\"oxirgi\":>8s} {\"oqilgan\":>9s} {\"lag\":>6s}') for tp in sorted(tps, key=lambda t: t.partition): komit = c.committed(tp) or 0 lag = oxiri[tp] - komit jami_lag += lag print(f'{tp.partition:>10d} {oxiri[tp]:>8d} {komit:>9d} {lag:>6d}') c.close() print(f'{\"JAMI LAG\":>10s} {jami_lag:>25d}') " 2>&1 | grep -v Warning
partition oxirgi oqilgan lag
0 712 712 0
1 642 642 0
2 646 646 0
JAMI LAG 0
Lag nol — bashoratchi oqimni quvib yetgan.
Bu raqam ishlab chiqarishdagi eng muhim signal. U o'sib borsa — model hodisalarga ulgurmayapti.
Lag o'sishi — kechikish emas, **qulash** belgisi
So'rov-javobda yuk oshsa, p99 sekin o'sadi va siz sezasiz.
Oqimda esa boshqacha: model soniyasiga 1000 hodisani uddalaydi, oqimga 1200 kelyapti. Farq atigi 200 — kichik ko'rinadi.
Lekin bu farq to'planadi. Bir soatda 720 000 hodisa orqada qolasiz. Ertalabga millionlab.
Va eng yomoni: grafik chiziqli o'sadi, shuning uchun uni erta payqash oson — agar qarayotgan bo'lsangiz.
Ogohlantirish qoidasi (34-bob): lag doimiy o'sayotgan bo'lsa xabar bering. Absolut son emas, yo'nalish muhim.
Onlayn xususiyatlar: eng qiyin qism
Bizning xususiyatlar oson edi — hammasi hodisaning ichida keldi.
Haqiqiy tizimda ko'pincha bunday bo'lmaydi. Masalan: "bu telefon oxirgi 7 kunda nechta e'londa uchradi?"
Bu savol tarixni talab qiladi. Hodisada u yo'q.
Uch yechim bor:
import pandas as pd show(pd.DataFrame([ {"Yechim": "Hodisaga qo'shib yuborish", "Kim hisoblaydi": "Manba tizimi", "Afzalligi": "Model sodda", "Kamchiligi": "Manba o'zgarishi kerak"}, {"Yechim": "Oqimda hisoblash (14-bob)", "Kim hisoblaydi": "Oyna + holat", "Afzalligi": "Tez, yangi", "Kamchiligi": "Murakkab, holat saqlanadi"}, {"Yechim": "Xususiyat ombori", "Kim hisoblaydi": "Alohida tizim", "Afzalligi": "O'qitish va xizmat bitta manba", "Kamchiligi": "Yana bitta tizim"}, ]))
| Yechim | Kim hisoblaydi | Afzalligi | Kamchiligi |
|---|---|---|---|
| Hodisaga qo'shib yuborish | Manba tizimi | Model sodda | Manba o'zgarishi kerak |
| Oqimda hisoblash (14-bob) | Oyna + holat | Tez, yangi | Murakkab, holat saqlanadi |
| Xususiyat ombori | Alohida tizim | O'qitish va xizmat bitta manba | Yana bitta tizim |
Xususiyat ombori (feature store) — 3-bobdagi muammoning sanoat yechimi: xususiyat bir marta hisoblanadi, o'qitish ham, xizmat ham o'sha qiymatni oladi.
Kichik loyihada u ortiqcha. Lekin o'nlab model va yuzlab xususiyat bo'lganda, u skewni butunlay yo'q qiladi.
Tozalash
docker rm -f kitob-kafka > /dev/null 2>&1 && echo "kafka o'chirildi" docker ps --format '{{.Names}}' | grep -v k3d
kafka o'chirildi novavivo-postgres novavivo-redis stratix-med-frontend-1
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| So'rov-javob | Foydalanuvchi kutadi; p99 muhim |
| Oqimli | Hech kim kutmaydi; lag muhim |
| Oqimda rad etish yo'q | Hodisa navbatda kutadi — yo'qolmaydi |
| To'plam | Oqimda bepul keladi (hech kim kutmaydi) |
| Skew xavfi | Oqim kodi ko'pincha alohida yoziladi — eng xavfli joy |
| Yechim | tayyorla() ni import qiling, qayta yozmang |
model_versiya |
Har natijaga yozing — keyin so'raladi |
| Lag o'sishi | Kechikish emas, to'planuvchi qarz |
| Ogohlantirish | Absolut son emas, yo'nalish |
| Onlayn xususiyat | Tarix kerak bo'lsa — oqimda hisoblang yoki ombor quring |
Talaba ishi
- Bashoratchini to'plamsiz (bittalab) ishlating. Elon/s qanday o'zgardi?
elon-kirishga 10 000 hodisa yuboring, lekin bashoratchini ishga tushirmang. Lag nima ko'rsatadi?enable_auto_commit=Trueqiling va bashoratchini yarmida to'xtating. Qayta ishga tushirganda nima bo'ldi va nega bu xavfli?- Natija oqimiga 34-bobdagi o'lchovlarni qo'shing: qaror ulushi vaqt bo'yicha.
- "Oxirgi 7 kundagi telefon takrorlanishi" xususiyatini 14-bobdagi oyna bilan hisoblang.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.