ใน production environment ที่ต้องรับ LLM request จำนวนมาก ปัญหาที่พบบ่อยที่สุดคือ gateway process ค้างหรือ僵死 (hang) โดยไม่มีสัญญาณเตือนจนกว่าจะมี user แจ้งเข้ามา ในบทความนี้ผมจะสอนวิธีใช้ sd_notify และ systemd watchdog เพื่อทำให้ LLM gateway process ของคุณ รู้ตัวว่าตัวเองตายแล้วกู้คืนภายในวินาที พร้อม codebase ที่พร้อมใช้งานจริง
ทำไมต้องใช้ sd_notify + systemd watchdog
ในระบบ LLM gateway แบบ single-process หรือ multi-worker ที่ deploy บน Linux server ปัญหาที่พบบ่อยมาก:
- Memory leak - ทำให้ process ค่อยๆ กิน RAM จนค้าง
- Connection pool exhaustion - HTTP connection ค้างทำให้ไม่สามารถรับ request ใหม่
- Deadlock - async code ติด loop ทำให้ event loop หยุดทำงาน
- Segfault - C extension หรือ native library crash
โดยปกติ systemd จะรู้ว่า process ตายแล้วถึงจะ restart แต่ถ้า process ยังไม่ตายแต่ค้าง (zombie/undead) systemd จะไม่รู้ตัว นี่คือจุดที่ sd_notify เข้ามาช่วย - มันคือ mechanism ให้ process ส่งสัญญาณ "ฉันยังมีชีวิตอยู่" ไปหา systemd ทุกๆ ช่วงเวลาที่กำหนด ถ้า systemd ไม่ได้รับสัญญาณภายใน timeout จะถือว่า process ค้างและ kill แล้ว restart ทันที
สถาปัตยกรรมระบบ
ก่อนจะเข้าสู่โค้ด มาดูภาพรวมของสถาปัตยกรรมที่เราจะสร้าง:
┌─────────────────────────────────────────────────────────────────┐
│ systemd │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ WatchdogSec=30s RestartSec=5s Restart=always │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ llm-gateway.service │ │ │
│ │ │ ┌────────────────────────────────────────────┐ │ │ │
│ │ │ │ Python/Go Process (sd_notify enabled) │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │ │
│ │ │ │ │ Watchdog │ │ LLM Gateway │ │ │ │ │
│ │ │ │ │ Timer │ │ Handler │ │ │ │ │
│ │ │ │ │ (every 10s) │ │ /v1/chat/complet │ │ │ │ │
│ │ │ │ └──────┬───────┘ └────────┬─────────┘ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ └────────┬─────────┘ │ │ │ │
│ │ │ │ │ sd_notify() │ │ │ │
│ │ │ │ └──────────────────────▶│ │ │ │
│ │ │ └────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
หลักการคือ Watchdog timer ภายใน process จะส่ง sd_notify ทุก 10 วินาที ในขณะที่ systemd กำหนด WatchdogSec=30s หมายความว่าถ้า 30 วินาทีผ่านไปโดยไม่มี signal รับ จะ auto-kill แล้ว restart
Python Implementation - sd_notify wrapper
เริ่มจาก Python implementation ที่ใช้ได้กับ FastAPI หรือ uvicorn:
# sd_notify_watchdog.py
import os
import socket
import threading
import time
from typing import Optional
class SystemdWatchdog:
"""
sd_notify wrapper for systemd watchdog integration.
Sends WATCHDOG=1 every interval to keep systemd informed.
"""
def __init__(self, interval: float = 10.0):
self.interval = interval
self._sock: Optional[socket.socket] = None
self._timer: Optional[threading.Timer] = None
self._enabled = os.environ.get("NOTIFY_SOCKET") is not None
self._stop_event = threading.Event()
def _get_socket(self) -> Optional[socket.socket]:
"""Get or create the notification socket."""
if not self._enabled:
return None
notify_socket = os.environ.get("NOTIFY_SOCKET")
if not notify_socket:
return None
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.connect(notify_socket)
return sock
except (socket.error, OSError) as e:
print(f"[Watchdog] Failed to connect to {notify_socket}: {e}")
return None
def _notify(self, status: str = "READY=1\nSTATUS=Running"):
"""Send notification to systemd."""
if not self._enabled:
return
try:
if self._sock is None:
self._sock = self._get_socket()
if self._sock:
msg = status.encode('utf-8')
self._sock.sendall(msg)
print(f"[Watchdog] Sent: {status.split()[0]}")
except socket.error as e:
print(f"[Watchdog] Send failed: {e}")
self._sock = None # Force reconnection on next attempt
def notify_ready(self):
"""Notify systemd that service is ready."""
self._notify("READY=1\nSTATUS=Ready to accept connections")
def _watchdog_loop(self):
"""Internal watchdog pulse loop."""
while not self._stop_event.wait(self.interval):
if self._stop_event.is_set():
break
self._notify(f"WATCHDOG=1\nSTATUS=Healthy (pid={os.getpid()})")
def start(self):
"""Start the watchdog timer."""
if not self._enabled:
print("[Watchdog] NOTIFY_SOCKET not set, watchdog disabled")
return
print(f"[Watchdog] Starting with {self.interval}s interval")
self.notify_ready()
self._timer = threading.Thread(target=self._watchdog_loop, daemon=True)
self._timer.start()
def stop(self):
"""Stop the watchdog timer."""
self._stop_event.set()
if self._timer:
self._timer.join(timeout=5)
if self._sock:
self._sock.close()
print("[Watchdog] Stopped")
def is_enabled(self) -> bool:
return self._enabled
Singleton instance
_watchdog: Optional[SystemdWatchdog] = None
def get_watchdog(interval: float = 10.0) -> SystemdWatchdog:
"""Get or create global watchdog instance."""
global _watchdog
if _watchdog is None:
_watchdog = SystemdWatchdog(interval)
return _watchdog
FastAPI Integration with HolySheep LLM Gateway
ต่อไปคือ FastAPI application ที่รวม watchdog เข้ากับ LLM gateway โดยใช้ HolySheep AI เป็น backend:
# llm_gateway.py
import os
import asyncio
from contextlib import asynccontextmanager
from typing import List, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
from sd_notify_watchdog import get_watchdog
HolySheep API Configuration
HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" # ห้ามใช้ api.openai.com
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
model: str = "gpt-4.1"
messages: List[Message]
temperature: float = 0.7
max_tokens: Optional[int] = 2048
class ChatResponse(BaseModel):
id: str
model: str
content: str
usage: dict
latency_ms: float
app = FastAPI(title="LLM Gateway with Watchdog", version="2.0")
@app.on_event("startup")
async def startup():
"""Initialize watchdog on startup."""
watchdog = get_watchdog(interval=10.0)
watchdog.start()
print("[Gateway] Started with systemd watchdog integration")
@app.on_event("shutdown")
async def shutdown():
"""Cleanup watchdog on shutdown."""
watchdog = get_watchdog()
watchdog.stop()
print("[Gateway] Shutdown complete")
@app.get("/health")
async def health_check():
"""Health check endpoint for load balancer."""
return {
"status": "healthy",
"pid": os.getpid(),
"watchdog_enabled": get_watchdog().is_enabled()
}
@app.post("/v1/chat/completions", response_model=ChatResponse)
async def chat_completions(request: ChatRequest):
"""
Proxy to HolySheep LLM API with automatic failover and retry.
"""
import time
start_time = time.time()
headers = {
"Authorization": f"Bearer {HOLYSHEEP_API_KEY}",
"Content-Type": "application/json"
}
payload = {
"model": request.model,
"messages": [{"role": m.role, "content": m.content} for m in request.messages],
"temperature": request.temperature,
"max_tokens": request.max_tokens
}
try:
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
f"{HOLYSHEEP_BASE_URL}/chat/completions",
headers=headers,
json=payload
)
response.raise_for_status()
data = response.json()
latency_ms = (time.time() - start_time) * 1000
return ChatResponse(
id=data.get("id", "unknown"),
model=data.get("model", request.model),
content=data["choices"][0]["message"]["content"],
usage=data.get("usage", {}),
latency_ms=round(latency_ms, 2)
)
except httpx.TimeoutException:
raise HTTPException(status_code=504, detail="LLM request timeout")
except httpx.HTTPStatusError as e:
raise HTTPException(status_code=e.response.status_code, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Gateway error: {str(e)}")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
systemd Service Unit File
ไฟล์ service ที่ต้องสร้างใน /etc/systemd/system/llm-gateway.service:
[Unit]
Description=LLM Gateway with Watchdog Auto-Recovery
After=network.target
[Service]
Type=notify
สำคัญ: Type=notify บอก systemd ว่า process จะส่ง READY signal
ถ้าใช้ ExecStart ตรงๆ ต้องใส่ --notify flag สำหรับ uvicorn
User=www-data
Group=www-data
WorkingDirectory=/opt/llm-gateway
Python/FastAPI startup
ExecStart=/opt/llm-gateway/venv/bin/python /opt/llm-gateway/llm_gateway.py
Watchdog configuration - systemd จะคอยรับ WATCHDOG=1 signal
WatchdogSec=30
RestartSec=5
Restart=always
Environment variables
Environment="HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY"
Environment="PYTHONUNBUFFERED=1"
Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=llm-gateway
Resource limits
LimitNOFILE=65536
MemoryMax=2G
[Install]
WantedBy=multi-user.target
หลังจากสร้างไฟล์แล้ว อย่าลืม reload systemd และ enable service:
sudo systemctl daemon-reload
sudo systemctl enable llm-gateway.service
sudo systemctl start llm-gateway.service
ตรวจสอบสถานะ
sudo systemctl status llm-gateway.service
ดู logs
sudo journalctl -u llm-gateway.service -f
ตารางเปรียบเทียบราคาและคุณสมบัติ
| API Provider | ราคา GPT-4.1 ($/MTok) |
ราคา Claude 4.5 ($/MTok) |
ราคา Gemini 2.5 Flash ($/MTok) |
ราคา DeepSeek V3.2 ($/MTok) |
ความหน่วง (Latency) | วิธีชำระเงิน | Free Credit |
|---|---|---|---|---|---|---|---|
| HolySheep AI | $8.00 | $15.00 | $2.50 | $0.42 | <50ms | WeChat, Alipay | ✓ มี |
| OpenAI (Official) | $15.00 | - | - | - | 100-300ms | Credit Card | $5 |
| Anthropic (Official) | - | $18.00 | - | - | 150-500ms | Credit Card | $5 |
| Google AI | - | - | $3.50 | - | 80-200ms | Credit Card | $300 |
เหมาะกับใคร / ไม่เหมาะกับใคร
✓ เหมาะกับ:
- DevOps/Backend Engineer ที่ต้องการ deploy LLM gateway บน Linux server และต้องการ auto-recovery ที่ reliable
- Startup/Team ที่มี budget จำกัด - ประหยัด 85%+ เมื่อเทียบกับ API ทางการ ลดต้นทุน infrastructure อย่างมาก
- ทีมพัฒนา AI Product ที่ต้องการ latency ต่ำ (<50ms) สำหรับ real-time application
- ผู้ใช้ในตลาดเอเชีย - รองรับ WeChat/Alipay ทำให้ชำระเงินสะดวก
- ทีมที่ต้องการโมเดลหลากหลาย - เข้าถึง GPT-4.1, Claude 4.5, Gemini 2.5 Flash, DeepSeek V3.2 จาก single API
✗ ไม่เหมาะกับ:
- องค์กรที่ต้องการ enterprise SLA ที่มี guarantee เป็นลายลักษณ์อักษร
- ผู้ที่ต้องการใช้ API ทางการโดยตรง เพื่อความเข้ากันได้กับ official SDK 100%
- โปรเจกต์ที่ใช้ Claude computer use หรือ advanced tool use ที่ยังไม่รองรับในทุก provider
ราคาและ ROI
มาคำนวณ ROI กันเลย สมมติว่าทีมของคุณใช้งาน LLM ประมาณ 10 ล้าน tokens ต่อเดือน:
| Provider | ราคาต่อ MTok | ค่าใช้จ่ายต่อเดือน (10M tokens) |
ประหยัดต่อปี |
|---|---|---|---|
| OpenAI Official (GPT-4) | $15.00 | $150 | - |
| HolySheep (GPT-4.1) | $8.00 | $80 | $840/ปี |
| DeepSeek V3.2 on HolySheep | $0.42 | $4.20 | $1,750/ปี |
แค่ใช้ DeepSeek V3.2 สำหรับ simple tasks และ GPT-4.1 สำหรับ complex tasks ก็ประหยัดได้มากกว่า $1,000/ปี แล้ว ยังไม่รวมกับ latency ที่ต่ำกว่าทำให้ user experience ดีขึ้น
ทำไมต้องเลือก HolySheep
จากประสบการณ์ตรงในการ deploy LLM gateway หลายตัว ข้อดีหลักของ HolySheep AI:
- อัตราแลกเปลี่ยน ¥1=$1 - ประหยัด 85%+ สำหรับผู้ใช้ในเอเชีย โดยเฉพาะจีนที่ใช้ WeChat/Alipay
- Multi-model ใน API เดียว - เปลี่ยน model ง่ายโดยแก้ model name ไม่ต้องเปลี่ยน code structure
- Latency ต่ำกว่า 50ms - เหมาะสำหรับ real-time application เช่น chatbot, autocomplete
- OpenAI-compatible API - migrate จาก OpenAI ง่ายมาก เปลี่ยน base URL + key ก็ใช้ได้เลย
- เครดิตฟรีเมื่อลงทะเบียน - ทดลองใช้ก่อนตัดสินใจ
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
ข้อผิดพลาดที่ 1: systemd ไม่รับ WATCHDOG signal
อาการ: journalctl แสดง llm-gateway.service: Watchdog timeout แม้ว่า process จะทำงานปกติ
สาเหตุ: Type ไม่ตรงกับการใช้งาน หรือ NOTIFY_SOCKET ไม่ได้ set
# วิธีแก้ไข: ตรวจสอบว่า Type=notify ถูกต้อง
ไฟล์ /etc/systemd/system/llm-gateway.service
[Service]
Type=notify # ต้องเป็น notify สำหรับ sd_notify
ExecStart=/opt/llm-gateway/venv/bin/python /opt/llm-gateway/llm_gateway.py
หรือถ้าใช้ uvicorn ต้องใส่ --notify flag
ExecStart=/opt/llm-gateway/venv/bin/uvicorn llm_gateway:app --host 0.0.0.0 --port 8000 --notify
ตรวจสอบ environment
Environment="NOTIFY_SOCKET=/run/systemd/notify"
Reload แล้ว restart
sudo systemctl daemon-reload
sudo systemctl restart llm-gateway.service
ข้อผิดพลาดที่ 2: Memory leak ทำให้ process ค้าง
อาการ: Process รับ request ได้ปกติ แต่ค่อยๆ ใช้ RAM เพิ่มขึ้นจนสุดท้ายค้าง
# วิธีแก้ไข: เพิ่ม MemoryMax และ restart เมื่อ memory สูงเกิน
ไฟล์ /etc/systemd/system/llm-gateway.service
[Service]
Memory limit - systemd จะ kill เมื่อเกิน
MemoryMax=2G
MemorySwapMax=1G
หรือใช้ OOMScoreAdjust เพื่อให้ kernel ฆ่า process ก่อน system crash
OOMScoreAdjust=-500
เพิ่ม monitoring script ที่ restart เมื่อ memory สูง
สร้างไฟล์ /opt/llm-gateway/monitor.sh
#!/bin/bash
while true; do
MEM=$(ps -o rss= -p $(cat /run/llm-gateway.pid))
if [ "$MEM" -gt 1800000 ]; then # 1.8GB
systemctl restart llm-gateway
logger "LLM Gateway restarted due to high memory usage"
fi
sleep 30
done
ข้อผิดพลาดที่ 3: Connection pool exhaustion
อาการ: httpx client timeout หรือ "Connection pool full" error บ่อยๆ
# วิธีแก้ไข: ใช้ connection pool ที่ถูกต้องและ timeout ที่เหมาะสม
แก้ไขไฟล์ llm_gateway.py
import httpx
from contextlib import asynccontextmanager
Global client ที่ reuse connection
_http_client: Optional[httpx.AsyncClient] = None
async def get_http_client() -> httpx.AsyncClient:
"""Get or create shared HTTP client with proper pooling."""
global _http_client
if _http_client is None:
limits = httpx.Limits(
max_keepalive_connections=20,
max_connections=100,
keepalive_expiry=30.0
)
_http_client = httpx.AsyncClient(
timeout=httpx.Timeout(60.0, connect=10.0),
limits=limits,
http2=True # HTTP/2 for better multiplexing
)
return _http_client
@app.on_event("