Blue-green, canary va shadow: xavfsiz chiqarish
O'ttiz bir soniya ham ko'p
32-bobda qaytarish ishladi: buzilgan versiya chiqdi, sog'liq tekshiruvi uni ushladi, tizim eski versiyaga qaytdi. Jami 31 soniya.
Yaxshi natija. Lekin o'sha 31 soniyada nima bo'ldi?
Foydalanuvchilar buzilgan servisga tushdi. Ular xato ko'rdi, sahifa ochilmadi, ba'zilari ketdi.
Savol: ularni umuman tegizmaslik mumkinmi?
Yangi oshpaz kelganda
Restoranga yangi oshpaz ishga oldingiz. U yaxshi taom pishiradimi — bilmaysiz.
Uch xil yo'l bor.
Birinchi yo'l. Ertadan boshlab u hamma taomni pishiradi. Eski oshpaz uyda. Agar yangisi yomon bo'lsa — butun kechki smena buziladi.
Bu — 32-bobdagi usul. Qaytish bor, lekin zarar yetgandan keyin.
Ikkinchi yo'l. Yangi oshpaz bitta stolga pishiradi, qolganiga eskisi. Yigirma stoldan bittasi. Yomon bo'lsa — bitta stol noroziligi, yigirmata emas.
Bu — canary.
Uchinchi yo'l. Yangi oshpaz har buyurtmani pishiradi, lekin uning taomi mijozga berilmaydi. Chiqadi, ko'riladi, eskisiniki bilan solishtiriladi, keyin tashlanadi.
Mijoz eski oshpazning taomini yeydi va yangisi borligini bilmaydi ham.
Bu — shadow (soya). Xavf nol.
import pandas as pd show(pd.DataFrame([ {"Usul": "To'g'ridan chiqarish", "Xavf": "To'liq", "Yangiga trafik": "100%", "Xatoni qachon bilasiz": "Foydalanuvchi ko'rgandan keyin"}, {"Usul": "Blue-green", "Xavf": "To'liq, qaytish tez", "Yangiga trafik": "0% -> 100%", "Xatoni qachon bilasiz": "Almashtirgandan keyin"}, {"Usul": "Canary", "Xavf": "Qisman", "Yangiga trafik": "5% -> 100%", "Xatoni qachon bilasiz": "Kichik guruh ko'rganda"}, {"Usul": "Shadow", "Xavf": "**Nol**", "Yangiga trafik": "100% (javob tashlanadi)", "Xatoni qachon bilasiz": "Hech kim ko'rmasdan"}, ]))
| Usul | Xavf | Yangiga trafik | Xatoni qachon bilasiz |
|---|---|---|---|
| To'g'ridan chiqarish | To'liq | 100% | Foydalanuvchi ko'rgandan keyin |
| Blue-green | To'liq, qaytish tez | 0% -> 100% | Almashtirgandan keyin |
| Canary | Qisman | 5% -> 100% | Kichik guruh ko'rganda |
| Shadow | **Nol** | 100% (javob tashlanadi) | Hech kim ko'rmasdan |
Blue-green: ikki oshxona
Ikki bir xil muhit turadi: blue (hozir ishlayotgan) va green (yangi).
Foydalanuvchi faqat bittasiga tushadi. Yangi versiya green da ko'tariladi, sinaladi, keyin proksi bir zumda green ga o'giriladi.
Nima foyda beradi? Qaytish bir soniya — proksini orqaga o'girasiz, xolos. Konteyner ko'tarish kutilmaydi.
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') " docker images mlops-servis:v1 -q | grep -q . || docker build -q -f Dockerfile.servis -t mlops-servis:v1 . > /dev/null 2>&1 docker rm -f mlops-blue mlops-green > /dev/null 2>&1 docker run -d --name mlops-blue -p 18140:8000 -e BROKER_CHEGARASI=0.5 mlops-servis:v1 > /dev/null 2>&1 docker run -d --name mlops-green -p 18141:8000 -e BROKER_CHEGARASI=0.35 mlops-servis:v1 > /dev/null 2>&1 for P in 18140 18141; do for i in $(seq 1 40); do [ "$(curl -s -o /dev/null -w %{http_code} http://127.0.0.1:${P}/ready 2>/dev/null)" = "200" ] && break sleep 0.5 done done echo -n "BLUE (chegara 0.5) : "; curl -s http://127.0.0.1:18140/ready echo "" echo -n "GREEN (chegara 0.35): "; curl -s http://127.0.0.1:18141/ready echo ""
model tayyor
BLUE (chegara 0.5) : {"holat":"tayyor","sorovlar":0,"chegara":0.5}
GREEN (chegara 0.35): {"holat":"tayyor","sorovlar":0,"chegara":0.35}
Ikki muhit ishlayapti. green da chegara boshqa — 27-bobdagi BROKER_CHEGARASI. Ya'ni
u boshqacha qaror qabul qiladi.
Blue-green ikki barobar resurs talab qiladi
Almashtirish paytida ikkala muhit ishlaydi. Ya'ni server, xotira, ma'lumotlar bazasi ulanishlari — hammasi ikki barobar.
Kichik servisda bu muammo emas. Lekin 20 GB xotira talab qiladigan model uchun bu 40 GB degani.
Bundan tashqari ma'lumotlar bazasi umumiy bo'ladi. Agar yangi versiya sxemani o'zgartirsa, eski versiya buziladi — va siz qayta olmaysiz. Shuning uchun 32-bobdagi qoida: sxema o'zgarishi kod chiqarishdan alohida bo'lsin.
Canary: bitta stol
Canary da trafik bo'linadi: 95% eski versiyaga, 5% yangisiga.
Nomi konchilardan kelgan: shaxtaga kanareyka olib tushishardi. Qush gazdan birinchi bo'lib ta'sirlanadi — u jim qolsa, odamlar chiqib ketadi.
shu faylni yaratingnginx-canary.conf
events {}
http {
# 95% blue, 5% green
split_clients "${remote_addr}${request_id}" $manzil {
5% green;
* blue;
}
upstream blue { server host.docker.internal:18140; }
upstream green { server host.docker.internal:18141; }
server {
listen 80;
location / {
proxy_pass http://$manzil;
proxy_set_header Host $host;
add_header X-Versiya $manzil always;
}
}
}
split_clients so'rovni barqaror taqsimlaydi: bir xil mijoz doim bir xil versiyaga
tushadi. Bu muhim — foydalanuvchi har bosishda boshqa xulq ko'rmasligi kerak.
docker rm -f mlops-canary > /dev/null 2>&1 docker run -d --name mlops-canary -p 18142:80 \ --add-host host.docker.internal:host-gateway \ -v "$(pwd)/nginx-canary.conf:/etc/nginx/nginx.conf:ro" \ nginx:1.27-alpine > /dev/null 2>&1 for i in $(seq 1 40); do [ "$(curl -s -o /dev/null -w %{http_code} http://127.0.0.1:18142/healthz 2>/dev/null)" = "200" ] && break sleep 0.5 done python3 -c " import urllib.request, collections n = collections.Counter() for i in range(200): r = urllib.request.Request('http://127.0.0.1:18142/healthz') try: with urllib.request.urlopen(r, timeout=5) as j: n[j.headers.get('X-Versiya', '?')] += 1 except Exception: n['xato'] += 1 print('200 sorov taqsimoti:') for k, v in sorted(n.items()): print(f' {k:8s}: {v:>4} ({v/2:.1f}%)') "
200 sorov taqsimoti: blue : 190 (95.0%) green : 10 (5.0%)
Taqsimot 5% ga yaqin chiqdi. Endi kuzatish kerak: green dagi xatolar soni, kechikish, bashorat taqsimoti. Yomonlashsa — 0% ga qaytarasiz.
Canary faqat kuzatuv bilan ma'noga ega
5% trafik yuborib, natijani kuzatmasangiz, siz shunchaki 5% foydalanuvchini xavf ostiga qo'ydingiz va hech narsa bilmadingiz.
Canary uchun kamida uchta o'lchov kerak:
- Xato ulushi (5xx) — ikki versiyada alohida;
- Kechikish p99 — 29-bobdagi kabi;
- Biznes ko'rsatkichi — bizda: broker deb belgilangan e'lonlar ulushi.
Uchinchisi eng muhim va eng ko'p unutiladi. Servis sog'lom bo'lishi mumkin, lekin model ikki barobar ko'p e'lonni broker deb belgilayotgan bo'lishi mumkin.
Shadow: xavfsiz solishtirish
Canary da 5% foydalanuvchi haqiqatan yangi versiyani ko'radi. ML uchun bu hali ham xavfli: model noto'g'ri qaror qilsa, o'sha 5% zarar ko'radi.
Shadow boshqacha ishlaydi:
- So'rov ikkala versiyaga yuboriladi;
- Foydalanuvchiga faqat eskisining javobi qaytadi;
- Yangisining javobi yozib olinadi va solishtiriladi.
Foydalanuvchi uchun hech narsa o'zgarmaydi. Siz esa yangi model haqiqiy trafikda qanday qaror qilishini ko'rasiz.
python3 -c " import json, urllib.request, collections import pandas as pd d = pd.read_csv('../datasets/ijara.csv').head(300) XUS = ['xonalar','maydon','qavat','jami_qavat','narx_mln','tavsif_uzunligi', 'rasmlar_soni','telefon_takrorlanishi','kunlik_elonlar','egasi_tasdiqlangan'] def sora(port, elon): r = urllib.request.Request(f'http://127.0.0.1:{port}/bashorat', data=json.dumps(elon).encode(), headers={'Content-Type': 'application/json'}) with urllib.request.urlopen(r, timeout=10) as j: return json.loads(j.read()) farq = 0 blue_broker = green_broker = 0 ehtimollar = [] for e in d[XUS].to_dict('records'): b = sora(18140, e) # BLUE - foydalanuvchi shuni oladi g = sora(18141, e) # GREEN - soya, javob tashlanadi blue_broker += b['broker'] green_broker += g['broker'] if b['broker'] != g['broker']: farq += 1 ehtimollar.append(abs(b['broker_ehtimoli'] - g['broker_ehtimoli'])) n = len(d) print(f'{n} ta elon, SOYA solishtiruvi:') print(f' BLUE broker dedi : {blue_broker:>4} ({blue_broker/n*100:.1f}%)') print(f' GREEN broker dedi : {green_broker:>4} ({green_broker/n*100:.1f}%)') print(f' QAROR FARQI : {farq:>4} ({farq/n*100:.1f}%)') print(f' ehtimol farqi : {max(ehtimollar):.4f} (eng katta)') print() print('Foydalanuvchi FAQAT blue javobini oldi. Green hech kimga korinmadi.') "
300 ta elon, SOYA solishtiruvi: BLUE broker dedi : 106 (35.3%) GREEN broker dedi : 122 (40.7%) QAROR FARQI : 16 (5.3%) ehtimol farqi : 0.0000 (eng katta) Foydalanuvchi FAQAT blue javobini oldi. Green hech kimga korinmadi.
Natijaga qarang. Ikki versiya bir xil model ishlatadi — farq faqat chegarada (0.5 va 0.35).
Ehtimol farqi nol, chunki model bir xil. Lekin qaror farqi katta: past chegara ko'proq e'lonni broker deb belgilaydi.
Mana shu — chiqarishdan oldin bilishingiz kerak bo'lgan raqam. Agar green ni jangga qo'ysangiz, broker deb belgilangan e'lonlar soni keskin oshadi. 1-bobdagi savol qaytadi: bu aniqlik yo'qotish hisobiga bo'ladimi?
Shadow ML uchun eng qimmatli usul
Oddiy dasturda shadow kamdan-kam ishlatiladi — kod to'g'ri yoki noto'g'ri, buni test ko'rsatadi.
ML da esa "to'g'ri" degan tushuncha yo'q. Model boshqacha qaror qiladi, va boshqacha yaxshiroqmi — buni faqat haqiqiy trafikda ko'rish mumkin.
Shadow aynan shuni beradi: haqiqiy so'rovlar, haqiqiy taqsimot, nol xavf.
Narxi — ikki barobar hisob. Lekin bir necha kunlik shadow, noto'g'ri model chiqarishdan ancha arzon.
Qaysi birini qachon
import pandas as pd show(pd.DataFrame([ {"Holat": "Kichik kod tuzatishi", "Tavsiya": "To'g'ridan + qaytarish (32-bob)"}, {"Holat": "Katta o'zgarish, tez qaytish kerak", "Tavsiya": "Blue-green"}, {"Holat": "Yangi xususiyat, foydalanuvchi xulqi noma'lum", "Tavsiya": "Canary"}, {"Holat": "**Yangi model**", "Tavsiya": "**Shadow**, keyin canary"}, {"Holat": "Ma'lumotlar bazasi sxemasi", "Tavsiya": "Hech biri — alohida, bosqichma-bosqich"}, ]))
| Holat | Tavsiya |
|---|---|
| Kichik kod tuzatishi | To'g'ridan + qaytarish (32-bob) |
| Katta o'zgarish, tez qaytish kerak | Blue-green |
| Yangi xususiyat, foydalanuvchi xulqi noma'lum | Canary |
| **Yangi model** | **Shadow**, keyin canary |
| Ma'lumotlar bazasi sxemasi | Hech biri — alohida, bosqichma-bosqich |
Model uchun tartib odatda shunday: shadow (bir necha kun, xavfsiz solishtirish) → canary (5%, haqiqiy foydalanuvchi) → to'liq.
Tozalash
docker rm -f mlops-blue mlops-green mlops-canary > /dev/null 2>&1 && echo "tozalandi" docker ps --format '{{.Names}}' | grep -v k3d
tozalandi novavivo-postgres novavivo-redis stratix-med-frontend-1
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Qaytarish yetarli emas | Foydalanuvchi baribir xatoni ko'radi |
| Blue-green | Ikki muhit; almashtirish va qaytish bir zumda |
| Blue-green narxi | Ikki barobar resurs; baza umumiy qoladi |
| Canary | Kichik ulush trafik; zarar cheklangan |
split_clients |
Bir xil mijoz doim bir xil versiyaga tushsin |
| Canary sharti | Kuzatuvsiz canary — shunchaki xavf |
| Shadow | Ikkala versiya ishlaydi, javob tashlanadi, xavf nol |
| Shadow ML uchun | "To'g'ri" emas, "boshqacha" — buni faqat real trafik ko'rsatadi |
| Model uchun tartib | Shadow → canary → to'liq |
Talaba ishi
- Canary ulushini 50% qiling va taqsimotni qayta o'lchang.
split_clientsqanchalik aniq? - Green ning chegarasini 0.7 qiling va shadow solishtiruvini qayta o'tkazing. Qaror farqi qaysi tomonga o'zgardi?
- Shadow natijasini faylga yozing: har e'lon uchun ikkala javob. Qanday tahlil qilasiz?
- Canary ga kechikish o'lchovini qo'shing (29-bob). Green sekinroqmi?
- Shadow ikki barobar yuk beradi (29-bob). Serverda bu qanday ko'rinadi va qanday cheklaysiz?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.