ผมเคยดูแลระบบ LLM gateway ที่รับโหลด 12 ล้าน request ต่อวัน ในช่วง Q1 ปี 2026 ทีมต้องย้าย traffic ทั้งหมดจาก upstream เดิมไปยัง HolySheep โดยไม่ให้เกิด downtime แม้แต่วินาทีเดียว บทความนี้คือ production playbook ที่เราใช้จริง — ตั้งแต่ canary routing แบบ consistent hashing, circuit breaker ที่ตัดสินใจภายใน 30 วินาที, ไปจนถึง cron job ที่ reconcile บิลทุกชั่วโมงเพื่อจับ drift ก่อนบิลงวดถัดไปปิด
ภาพรวมสถาปัตยกรรม Gray Release 3 เฟส
- เฟส 1 — Shadow traffic (5%, 7 วัน): ส่ง request จริงไปทั้งสอง upstream พร้อมกัน แต่ตอบกลับเฉพาะผลจาก primary เพื่อเก็บ diff log
- เฟส 2 — Canary (20%, 14 วัน): ปล่อย 20% ของ user hash ให้ใช้ HolySheep จริง พร้อม SLO guard rail
- เฟส 3 — Cutover (100%, 1 วัน): ย้ายน้ำหนักเป็น 100/0 ภายใต้เงื่อนไข success rate ≥ 99.5% และ P95 latency ≤ 220 ms
ชั้นที่ 1 — Gateway แบบ Canary Routing ด้วย Consistent Hashing
หัวใจของ gray release คือการเลือก upstream แบบ deterministic ต่อ request เพื่อให้ session เดิมไม่กระโดดไปมา เราใช้ MD5 ของ request_id + tenant_id แล้ว modulo ด้วยน้ำหนักรวม วิธีนี้ทำให้เวลาเลื่อน weight จาก 80/20 ไป 60/40 มีเพียง 20% ของ traffic ที่ย้าย ไม่ใช่ทั้งหมด
"""gateway.py — Enterprise Gray Release Gateway
ทดสอบ: python gateway.py
ต้องการ: pip install httpx
"""
import asyncio
import hashlib
import json
import time
import logging
from dataclasses import dataclass, field
from typing import Optional, Dict, Any
import httpx
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("gateway")
@dataclass
class ProviderConfig:
name: str
base_url: str
api_key: str
weight: int = 100 # เปอร์เซ็นต์ 0-100
healthy: bool = True
p95_latency_ms: float = 0.0
success_count: int = 0
failure_count: int = 0
class GrayReleaseGateway:
def __init__(self) -> None:
# ทั้งสอง pool ใช้ HolySheep แต่คนละ region/key
# เพื่อทดสอบ failover ระหว่าง edge node
self.providers: Dict[str, ProviderConfig] = {
"primary": ProviderConfig(
name="holysheep-primary",
base_url="https://api.holysheep.ai/v1",
api_key="YOUR_HOLYSHEEP_API_KEY",
weight=80,
),
"canary": ProviderConfig(
name="holysheep-canary",
base_url="https://api.holysheep.ai/v1",
api_key="YOUR_HOLYSHEEP_API_KEY",
weight=20,
),
}
self._lock = asyncio.Lock()
def _select_provider(self, request_id: str) -> ProviderConfig:
"""Deterministic routing ด้วย consistent hashing"""
key = f"tenant:{request_id}"
bucket = int(hashlib.md5(key.encode()).hexdigest(), 16) % 100
cumulative, fallback = 0, None
for cfg in self.providers.values():
if not cfg.healthy:
continue
cumulative += cfg.weight
fallback = fallback or cfg
if bucket < cumulative:
return cfg
return fallback or self.providers["primary"]
async def update_weights(self, primary_w: int, canary_w: int) -> None:
"""เรียกตอน promote/demote canary"""
async with self._lock:
self.providers["primary"].weight = primary_w
self.providers["canary"].weight = canary_w
log.info("weights updated primary=%d canary=%d",
primary_w, canary_w)
async def chat(self, request_id: str, payload: Dict[str, Any],
timeout: float = 8.0) -> Dict[str, Any]:
provider = self._select_provider(request_id)
start = time.perf_counter()
try:
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.post(
f"{provider.base_url}/chat/completions",
headers={
"Authorization": f"Bearer {provider.api_key}",
"X-Request-Id": request_id,
},
json=payload,
)
resp.raise_for_status()
elapsed_ms = (time.perf_counter() - start) * 1000
provider.p95_latency_ms = max(
provider.p95_latency_ms * 0.9, elapsed_ms)
provider.success_count += 1
return resp.json()
except Exception as exc:
provider.failure_count += 1
log.error("provider=%s err=%s", provider.name, exc)
# ส่งต่อให้ failover layer จัดการ
raise
if __name__ == "__main__":
async def _demo():
gw = GrayReleaseGateway()
for i in range(50):
req_id = f"req-{i:04d}"
provider = gw._select_provider(req_id)
print(f"{req_id} -> {provider.name}")
asyncio.run(_demo())
ชั้นที่ 2 — Circuit Breaker พร้อม Auto-Failover
Circuit breaker มี 3 state: CLOSED (ทำงานปกติ), OPEN (หยุดเรียก upstream เพื่อไม่ให้ท่วม), HALF_OPEN (ปล่อย probe จำนวนจำกัดเพื่อทดสอบ recovery) เราตั้ง threshold ไว้ที่ 5 ครั้งติดในหน้าต่าง 60 วินาที และ recovery timeout 30 วินาที ซึ่งค่านี้ได้มาจากการยิง load test จริงและดูว่า upstream ฟื้นตัวเร็วแค่ไหน
"""circuit_breaker.py — Auto-failover ระหว่าง HolySheep pool
ทดสอบ: python circuit_breaker.py
"""
import asyncio
import time
import random
from enum import Enum
from dataclasses import dataclass, field
from typing import Callable, Awaitable, Any
class State(str, Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"
@dataclass
class CircuitBreaker:
failure_threshold: int = 5
recovery_timeout_s: float = 30.0
half_open_max_probes: int = 3
latency_budget_ms: float = 220.0
state: State = State.CLOSED
failure_streak: int = 0
opened_at: float = 0.0
probe_attempts: int = 0
p95_latency_ms: float = 0.0
total_calls: int = 0
total_failures: int = 0
def allow(self) -> bool:
if self.state is State.CLOSED:
return True
if self.state is State.OPEN:
if time.time() - self.opened_at >= self.recovery_timeout_s:
self.state = State.HALF_OPEN
self.probe_attempts = 0
log.info("breaker -> HALF_OPEN")
return True
return False
# HALF_OPEN
return self.probe_attempts < self.half_open_max_probes
def record(self, ok: bool, latency_ms: float) -> None:
self.total_calls += 1
# EWMA แบบ alpha=0.1 สำหรับ P95 approximation
self.p95_latency_ms = (0.1 * latency_ms +
0.9 * self.p95