ใน 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 ปัญหาที่พบบ่อยมาก:

โดยปกติ 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

เหมาะกับใคร / ไม่เหมาะกับใคร

✓ เหมาะกับ:

✗ ไม่เหมาะกับ:

ราคาและ 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: 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("