ในโลกของการพัฒนา AI Application ยุคนี้ การเรียก AI API แบบ synchronous ไม่เพียงพออีกต่อไปแล้ว ในบทความนี้ ผมจะพาทุกคนไปดูวิธีการสร้างระบบ Go ที่รองรับ high concurrency ด้วย goroutine และ channel ซึ่งเป็นหัวใจหลักของ concurrency ในภาษา Go พร้อมกับแนะนำ HolySheep AI ที่ให้บริการ AI API คุณภาพสูงด้วย latency ต่ำกว่า 50ms และราคาประหยัดกว่า 85% เมื่อเทียบกับผู้ให้บริการรายอื่น

ทำไมต้อง High Concurrency สำหรับ AI API?

จากประสบการณ์การพัฒนาระบบ AI ของผม พบว่าการเรียก AI API แบบ sequential นั้นเป็น bottleneck ที่ใหญ่ที่สุด สมมติว่าคุณมี 1000 request ที่ต้องประมวลผล แต่ละ request ใช้เวลาเฉลี่ย 200ms ถ้าเรียกแบบลำดับ จะใช้เวลา 200 วินาที แต่ถ้าใช้ concurrency รองรับ 100 goroutine พร้อมกัน เวลาจะลดเหลือประมาณ 2 วินาทีเท่านั้น

ยิ่งไปกว่านั้น HolySheep AI มี infrastructure ที่รองรับ high throughput ด้วย latency เฉลี่ยต่ำกว่า 50ms ทำให้การใช้งาน goroutine มีประสิทธิภาพสูงสุด และด้วยราคาเพียง $0.42/MTok สำหรับ DeepSeek V3.2 ช่วยให้คุณประหยัดต้นทุนได้มหาศาล

สถาปัตยกรรมระบบ High Concurrency AI API

1. Worker Pool Pattern

Worker Pool เป็น pattern ที่ผมแนะนำเป็นอันดับแรกสำหรับการควบคุม concurrency อย่างมีประสิทธิภาพ แทนที่จะสร้าง goroutine ใหม่ทุก request (ซึ่งจะทำให้ memory explode) เราจะสร้าง pool ของ workers ที่ reuse กัน

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
    "time"

    "github.com/google/uuid"
)

// Config สำหรับ Worker Pool
type WorkerPoolConfig struct {
    NumWorkers     int
    QueueSize      int
    MaxRetries     int
    Timeout        time.Duration
    BaseURL        string
    APIKey         string
}

// Request/Response structures
type AIRequest struct {
    ID      string
    Model   string
    Prompt  string
    MaxTokens int
}

type AIResponse struct {
    ID      string
    Content string
    Usage   Usage
    Error   error
}

type Usage struct {
    PromptTokens     int
    CompletionTokens int
    TotalTokens      int
}

// Worker Pool Implementation
type AIWorkerPool struct {
    config     WorkerPoolConfig
    jobQueue   chan AIRequest
    resultQueue chan AIResponse
    wg         sync.WaitGroup
    client     *http.Client
}

func NewAIWorkerPool(config WorkerPoolConfig) *AIWorkerPool {
    pool := &AIWorkerPool{
        config:      config,
        jobQueue:    make(chan AIRequest, config.QueueSize),
        resultQueue: make(chan AIResponse, config.QueueSize),
        client: &http.Client{
            Timeout: config.Timeout,
            Transport: &http.Transport{
                MaxConnsPerHost:       config.NumWorkers,
                MaxIdleConnsPerHost:   config.NumWorkers,
                IdleConnTimeout:       90 * time.Second,
                ResponseHeaderTimeout: config.Timeout,
            },
        },
    }
    return pool
}

func (p *AIWorkerPool) Start() {
    for i := 0; i < p.config.NumWorkers; i++ {
        p.wg.Add(1)
        go p.worker(i)
    }
}

func (p *AIWorkerPool) worker(id int) {
    defer p.wg.Done()
    for req := range p.jobQueue {
        resp := p.callAIAPI(req)
        p.resultQueue <- resp
    }
}

func (p *AIWorkerPool) callAIAPI(req AIRequest) AIResponse {
    ctx, cancel := context.WithTimeout(context.Background(), p.config.Timeout)
    defer cancel()

    payload := map[string]interface{}{
        "model": req.Model,
        "messages": []map[string]string{
            {"role": "user", "content": req.Prompt},
        },
        "max_tokens": req.MaxTokens,
    }

    jsonData, _ := json.Marshal(payload)
    
    httpReq, err := http.NewRequestWithContext(ctx, "POST", 
        p.config.BaseURL+"/chat/completions", 
        bytes.NewBuffer(jsonData))
    if err != nil {
        return AIResponse{ID: req.ID, Error: err}
    }

    httpReq.Header.Set("Content-Type", "application/json")
    httpReq.Header.Set("Authorization", "Bearer "+p.config.APIKey)

    resp, err := p.client.Do(httpReq)
    if err != nil {
        return AIResponse{ID: req.ID, Error: err}
    }
    defer resp.Body.Close()

    var result map[string]interface{}
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return AIResponse{ID: req.ID, Error: err}
    }

    // Parse response...
    return AIResponse{
        ID:      req.ID,
        Content: "parsed content",
        Usage:   Usage{PromptTokens: 10, CompletionTokens: 50, TotalTokens: 60},
    }
}

func (p *AIWorkerPool) Submit(req AIRequest) bool {
    select {
    case p.jobQueue <- req:
        return true
    default:
        return false // Queue full
    }
}

func (p *AIWorkerPool) GetResult() (AIResponse, bool) {
    select {
    case resp := <-p.resultQueue:
        return resp, true
    default:
        return AIResponse{}, false
    }
}

func (p *AIWorkerPool) Shutdown() {
    close(p.jobQueue)
    p.wg.Wait()
    close(p.resultQueue)
}

import "bytes"

2. Rate Limiter ด้วย Token Bucket

การควบคุม rate limit เป็นสิ่งสำคัญมาก เพื่อไม่ให้เกิน quota ของ API provider ในที่นี้ผมใช้ Token Bucket algorithm ที่มีประสิทธิภาพกว่า Leaky Bucket

package main

import (
    "golang.org/x/time/rate"
    "sync"
    "time"
)

// TokenBucketRateLimiter - Rate limiter per API key
type TokenBucketRateLimiter struct {
    limiters map[string]*rate.Limiter
    mu       sync.RWMutex
    rps      rate.Limit
    burst    int
}

func NewTokenBucketRateLimiter(rps float64, burst int) *TokenBucketRateLimiter {
    return &TokenBucketRateLimiter{
        limiters: make(map[string]*rate.Limiter),
        rps:      rate.Limit(rps),
        burst:    burst,
    }
}

func (t *TokenBucketRateLimiter) GetLimiter(apiKey string) *rate.Limiter {
    t.mu.Lock()
    defer t.mu.Unlock()

    if limiter, exists := t.limiters[apiKey]; exists {
        return limiter
    }

    limiter := rate.NewLimiter(t.rps, t.burst)
    t.limiters[apiKey] = limiter
    return limiter
}

func (t *TokenBucketRateLimiter) Allow(apiKey string) bool {
    limiter := t.GetLimiter(apiKey)
    return limiter.Allow()
}

// AdvancedRateLimiter with per-model and per-key limits
type AdvancedRateLimiter struct {
    globalLimiter    *rate.Limiter
    modelLimiters    map[string]*rate.Limiter
    keyLimiters      map[string]*rate.Limiter
    mu               sync.RWMutex
    globalRPS        rate.Limit
    modelRPSTable    map[string]rate.Limit
}

func NewAdvancedRateLimiter(globalRPS float64, modelRPS map[string]float64) *AdvancedRateLimiter {
    arl := &AdvancedRateLimiter{
        globalLimiter: rate.NewLimiter(rate.Limit(globalRPS), int(globalRPS)),
        modelLimiters: make(map[string]*rate.Limiter),
        keyLimiters:   make(map[string]*rate.Limiter),
        globalRPS:     rate.Limit(globalRPS),
        modelRPSTable: make(map[string]rate.Limit),
    }

    for model, rps := range modelRPS {
        arl.modelRPSTable[model] = rate.Limit(rps)
        arl.modelLimiters[model] = rate.NewLimiter(rate.Limit(rps), int(rps))
    }

    return arl
}

func (arl *AdvancedRateLimiter) AllowRequest(apiKey, model string) bool {
    arl.mu.RLock()
    defer arl.mu.RUnlock()

    // Check global limit
    if !arl.globalLimiter.Allow() {
        return false
    }

    // Check model-specific limit
    if modelLimiter, exists := arl.modelLimiters[model]; exists {
        if !modelLimiter.Allow() {
            return false
        }
    }

    // Check per-key limit
    if keyLimiter, exists := arl.keyLimiters[apiKey]; exists {
        if !keyLimiter.Allow() {
            return false
        }
    }

    return true
}

func (arl *AdvancedRateLimiter) Wait(ctx context.Context, apiKey, model string) error {
    arl.mu.RLock()
    limiter := arl.globalLimiter
    if modelLimiter, exists := arl.modelLimiters[model]; exists {
        limiter = modelLimiter
    }
    arl.mu.RUnlock()

    return limiter.Wait(ctx)
}

// Usage example
func ExampleRateLimiter() {
    limiter := NewAdvancedRateLimiter(1000, map[string]float64{
        "gpt-4.1":         100,
        "claude-sonnet-4.5": 50,
        "deepseek-v3.2":   500,
    })

    // Per-key limiter with custom limits
    limiter.mu.Lock()
    limiter.keyLimiters["YOUR_HOLYSHEEP_API_KEY"] = rate.NewLimiter(200, 200)
    limiter.mu.Unlock()

    // Test allow
    if limiter.AllowRequest("YOUR_HOLYSHEEP_API_KEY", "deepseek-v3.2") {
        println("Request allowed")
    }
}

3. Circuit Breaker Pattern สำหรับ Fault Tolerance

เมื่อ API มีปัญหา เราต้องมี circuit breaker เพื่อป้องกัน cascade failure ผมใช้ pattern นี้มาหลายปีแล้ว ช่วยลดการเรียก API ที่จะ fail อย่างมาก

package main

import (
    "errors"
    "sync"
    "time"
)

var (
    ErrCircuitOpen      = errors.New("circuit breaker is open")
    ErrTooManyRequests  = errors.New("too many requests")
    ErrTimeout          = errors.New("request timeout")
)

// State of circuit breaker
type CircuitState int

const (
    StateClosed CircuitState = iota
    StateOpen
    StateHalfOpen
)

func (s CircuitState) String() string {
    switch s {
    case StateClosed:
        return "CLOSED"
    case StateOpen:
        return "OPEN"
    case StateHalfOpen:
        return "HALF-OPEN"
    default:
        return "UNKNOWN"
    }
}

// CircuitBreaker configuration
type CircuitBreakerConfig struct {
    FailureThreshold  int           // Number of failures before opening
    SuccessThreshold  int           // Number of successes in half-open before closing
    Timeout           time.Duration // Time before transitioning from open to half-open
    HalfOpenMaxCalls  int           // Max calls allowed in half-open state
}

// CircuitBreaker implementation
type CircuitBreaker struct {
    config     CircuitBreakerConfig
    state      CircuitState
    mu         sync.Mutex
    failures   int
    successes  int
    lastChange time.Time
    halfOpenCalls int
}

func NewCircuitBreaker(config CircuitBreakerConfig) *CircuitBreaker {
    return &CircuitBreaker{
        config:    config,
        state:     StateClosed,
        lastChange: time.Now(),
    }
}

func (cb *CircuitBreaker) State() CircuitState {
    cb.mu.Lock()
    defer cb.mu.Unlock()
    return cb.state
}

func (cb *CircuitBreaker) canAttempt() bool {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    switch cb.state {
    case StateClosed:
        return true
    case StateOpen:
        // Check if timeout has passed
        if time.Since(cb.lastChange) >= cb.config.Timeout {
            cb.transitionTo(StateHalfOpen)
            return true
        }
        return false
    case StateHalfOpen:
        return cb.halfOpenCalls < cb.config.HalfOpenMaxCalls
    default:
        return false
    }
}

func (cb *CircuitBreaker) transitionTo(state CircuitState) {
    cb.state = state
    cb.lastChange = time.Now()
    cb.failures = 0
    cb.successes = 0
    cb.halfOpenCalls = 0
}

func (cb *CircuitBreaker) RecordSuccess() {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    switch cb.state {
    case StateClosed:
        cb.failures = 0 // Reset on success
    case StateHalfOpen:
        cb.successes++
        cb.halfOpenCalls--
        if cb.successes >= cb.config.SuccessThreshold {
            cb.transitionTo(StateClosed)
        }
    }
}

func (cb *CircuitBreaker) RecordFailure() {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    switch cb.state {
    case StateClosed:
        cb.failures++
        if cb.failures >= cb.config.FailureThreshold {
            cb.transitionTo(StateOpen)
        }
    case StateHalfOpen:
        cb.transitionTo(StateOpen)
    }
}

func (cb *CircuitBreaker) Execute(fn func() error) error {
    if !cb.canAttempt() {
        return ErrCircuitOpen
    }

    cb.mu.Lock()
    if cb.state == StateHalfOpen {
        cb.halfOpenCalls++
    }
    cb.mu.Unlock()

    err := fn()
    if err != nil {
        cb.RecordFailure()
        return err
    }

    cb.RecordSuccess()
    return nil
}

// AI Circuit Breaker with model-specific tracking
type AICircuitBreaker struct {
    breakers map[string]*CircuitBreaker
    mu        sync.RWMutex
    defaultConfig CircuitBreakerConfig
}

func NewAICircuitBreaker(defaultConfig CircuitBreakerConfig) *AICircuitBreaker {
    return &AICircuitBreaker{
        breakers:     make(map[string]*CircuitBreaker),
        defaultConfig: defaultConfig,
    }
}

func (aicb *AICircuitBreaker) GetBreaker(model string) *CircuitBreaker {
    aicb.mu.RLock()
    breaker, exists := aicb.breakers[model]
    aicb.mu.RUnlock()

    if exists {
        return breaker
    }

    aicb.mu.Lock()
    defer aicb.mu.Unlock()

    if breaker, exists = aicb.breakers[model]; exists {
        return breaker
    }

    breaker = NewCircuitBreaker(aicb.defaultConfig)
    aicb.breakers[model] = breaker
    return breaker
}

func (aicb *AICircuitBreaker) Execute(model string, fn func() error) error {
    breaker := aicb.GetBreaker(model)
    return breaker.Execute(fn)
}

// Example usage with HolySheep API
func ExampleAICircuitBreaker() {
    cb := NewAICircuit