Asosiy qismga o'tish

Blue-green, canary va shadow: xavfsiz chiqarish

33-bob 7 kod bloki 1686 so'z

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

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.

python11 qator
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"},
]))
UsulXavfYangiga trafikXatoni qachon bilasiz
To'g'ridan chiqarishTo'liq100%Foydalanuvchi ko'rgandan keyin
Blue-greenTo'liq, qaytish tez0% -> 100%Almashtirgandan keyin
CanaryQisman5% -> 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.

bash32 qator
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 ""
Chiqish (qaytish kodi 0, 4654 ms)
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

python21 qator
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.

bash24 qator
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}%)')
"
Chiqish (qaytish kodi 0, 1750 ms)
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:

  1. So'rov ikkala versiyaga yuboriladi;
  2. Foydalanuvchiga faqat eskisining javobi qaytadi;
  3. Yangisining javobi yozib olinadi va solishtiriladi.

Foydalanuvchi uchun hech narsa o'zgarmaydi. Siz esa yangi model haqiqiy trafikda qanday qaror qilishini ko'rasiz.

python36 qator
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.')
"
Chiqish (qaytish kodi 0, 3050 ms)
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

python8 qator
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"},
]))
HolatTavsiya
Kichik kod tuzatishiTo'g'ridan + qaytarish (32-bob)
Katta o'zgarish, tez qaytish kerakBlue-green
Yangi xususiyat, foydalanuvchi xulqi noma'lumCanary
**Yangi model****Shadow**, keyin canary
Ma'lumotlar bazasi sxemasiHech biri — alohida, bosqichma-bosqich

Model uchun tartib odatda shunday: shadow (bir necha kun, xavfsiz solishtirish) → canary (5%, haqiqiy foydalanuvchi) → to'liq.

Tozalash

bash2 qator
docker rm -f mlops-blue mlops-green mlops-canary > /dev/null 2>&1 && echo "tozalandi"
docker ps --format '{{.Names}}' | grep -v k3d
Chiqish (qaytish kodi 0, 1278 ms)
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

  1. Canary ulushini 50% qiling va taqsimotni qayta o'lchang. split_clients qanchalik aniq?
  2. Green ning chegarasini 0.7 qiling va shadow solishtiruvini qayta o'tkazing. Qaror farqi qaysi tomonga o'zgardi?
  3. Shadow natijasini faylga yozing: har e'lon uchun ikkala javob. Qanday tahlil qilasiz?
  4. Canary ga kechikish o'lchovini qo'shing (29-bob). Green sekinroqmi?
  5. 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.

Telegramda yozish Narxlar