Trong quá trình xây dựng hệ thống AI pipeline tại HolySheep AI, tôi đã đối mặt với bài toán xử lý hàng nghìn request API mỗi ngày với chi phí tối ưu nhất. Bài viết này sẽ chia sẻ kinh nghiệm thực chiến về thiết kế và triển khai batch request system sử dụng MCP (Model Context Protocol) protocol — giúp giảm 85%+ chi phí API và cải thiện đáng kể throughput.

Tại sao cần Batch Request Optimization?

Khi làm việc với các model AI production, tôi nhận ra rằng việc gửi từng request riêng lẻ không chỉ tốn kém mà còn gây ra:

Architecture Design cho Batch MCP System

Kiến trúc batch request mà tôi áp dụng gồm 4 layer chính:

┌─────────────────────────────────────────────────────────────┐
│                    BATCH ORCHESTRATOR                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ Request     │  │ Priority    │  │ Concurrency         │  │
│  │ Queue       │→ │ Scheduler   │→ │ Controller          │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                    BATCH AGGREGATOR                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ Request     │  │ Batch       │  │ Adaptive            │  │
│  │ Buffer      │→ │ Batcher     │→ │ Timeout Manager     │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                    HOLYSHEEP API GATEWAY                     │
│              https://api.holysheep.ai/v1                     │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ Token       │  │ WeChat/     │  │ Free Credits        │  │
│  │ Bucket      │  │ Alipay      │  │ Management          │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Production Implementation

1. Core Batch Client với Connection Pooling

import asyncio
import aiohttp
import time
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any, Callable
from collections import defaultdict
import heapq

@dataclass
class BatchConfig:
    max_batch_size: int = 100
    max_wait_ms: int = 50
    max_concurrent_batches: int = 10
    retry_attempts: int = 3
    base_url: str = "https://api.holysheep.ai/v1"
    api_key: str = "YOUR_HOLYSHEEP_API_KEY"

@dataclass
class BatchRequest:
    id: str
    messages: List[Dict[str, str]]
    model: str = "deepseek-v3.2"
    temperature: float = 0.7
    max_tokens: int = 2048
    callback: Optional[Callable] = None

@dataclass
class BatchResponse:
    request_id: str
    content: Optional[str] = None
    error: Optional[str] = None
    latency_ms: float = 0.0
    tokens_used: int = 0

class BatchMCPClient:
    """
    Production-grade batch client cho MCP protocol
    Tích hợp HolySheep API với connection pooling và automatic batching
    """
    
    def __init__(self, config: BatchConfig):
        self.config = config
        self._pending_requests: Dict[str, BatchRequest] = {}
        self._pending_heap: List[tuple] = []  # (timestamp, request_id)
        self._lock = asyncio.Lock()
        self._session: Optional[aiohttp.ClientSession] = None
        self._semaphore = asyncio.Semaphore(config.max_concurrent_batches)
        self._metrics = defaultdict(int)
        
    async def initialize(self):
        """Khởi tạo connection pool với optimal settings"""
        connector = aiohttp.TCPConnector(
            limit=100,  # Connection pool size
            limit_per_host=50,
            ttl_dns_cache=300,
            enable_cleanup_closed=True
        )
        
        timeout = aiohttp.ClientTimeout(
            total=30,
            connect=5,
            sock_read=25
        )
        
        self._session = aiohttp.ClientSession(
            connector=connector,
            timeout=timeout,
            headers={
                "Authorization": f"Bearer {self.config.api_key}",
                "Content-Type": "application/json",
                "X-Batch-Enabled": "true"
            }
        )
        
    async def add_request(self, request: BatchRequest) -> str:
        """Thêm request vào batch queue với timeout tracking"""
        async with self._lock:
            self._pending_requests[request.id] = request
            heapq.heappush(
                self._pending_heap, 
                (time.time() * 1000, request.id)
            )
            self._metrics["requests_added"] += 1
            
        # Trigger batch processing nếu đủ điều kiện
        await self._maybe_flush_batch()
        return request.id
    
    async def _maybe_flush_batch(self):
        """Kiểm tra và flush batch nếu đạt threshold"""
        async with self._lock:
            current_time = time.time() * 1000
            
            # Check timeout expired requests
            while self._pending_heap:
                oldest_ts, oldest_id = self._pending_heap[0]
                if current_time - oldest_ts >= self.config.max_wait_ms:
                    heapq.heappop(self._pending_heap)
                    await self._execute_batch()
                else:
                    break
                    
            # Check batch size threshold
            if len(self._pending_requests) >= self.config.max_batch_size:
                await self._execute_batch()
    
    async def _execute_batch(self):
        """Execute batch request lên HolySheep API"""
        if not self._pending_requests:
            return
            
        requests_to_process = dict(self._pending_requests)
        self._pending_requests.clear()
        self._pending_heap.clear()
        
        async with self._semaphore:
            start_time = time.time()
            
            try:
                # Format batch request theo MCP protocol
                batch_payload = self._format_batch_payload(requests_to_process)
                
                async with self._session.post(
                    f"{self.config.base_url}/chat/completions",
                    json=batch_payload,
                    params={"batch_mode": "true"}
                ) as response:
                    if response.status == 200:
                        results = await response.json()
                        await self._dispatch_results(requests_to_process, results)
                        self._metrics["batches_success"] += 1
                    else:
                        error_text = await response.text()
                        await self._handle_batch_error(requests_to_process, error_text)
                        self._metrics["batches_failed"] += 1
                        
            except Exception as e:
                await self._handle_batch_error(requests_to_process, str(e))
                self._metrics["batches_failed"] += 1
                
            self._metrics["total_latency_ms"] += (time.time() - start_time) * 1000
    
    def _format_batch_payload(self, requests: Dict[str, BatchRequest]) -> Dict:
        """Format batch payload theo MCP v2 spec"""
        return {
            "batched_requests": [
                {
                    "custom_id": req.id,
                    "body": {
                        "model": req.model,
                        "messages": req.messages,
                        "temperature": req.temperature,
                        "max_tokens": req.max_tokens
                    }
                }
                for req in requests.values()
            ]
        }
    
    async def _dispatch_results(
        self, 
        requests: Dict[str, BatchRequest], 
        results: Dict
    ):
        """Dispatch kết quả về callback handlers"""
        for item in results.get("batched_results", []):
            request_id = item.get("custom_id")
            if request_id in requests:
                request = requests[request_id]
                response = BatchResponse(
                    request_id=request_id,
                    content=item.get("choices", [{}])[0].get("message", {}).get("content"),
                    latency_ms=item.get("latency_ms", 0),
                    tokens_used=item.get("usage", {}).get("total_tokens", 0)
                )
                
                if request.callback:
                    await request.callback(response)
                    
    async def _handle_batch_error(
        self, 
        requests: Dict[str, BatchRequest], 
        error: str
    ):
        """Handle batch error với retry logic"""
        for request_id, request in requests.items():
            response = BatchResponse(
                request_id=request_id,
                error=error
            )
            if request.callback:
                await request.callback(response)
    
    def get_metrics(self) -> Dict[str, Any]:
        """Lấy metrics cho monitoring"""
        total_batches = self._metrics["batches_success"] + self._metrics["batches_failed"]
        return {
            "total_requests": self._metrics["requests_added"],
            "batches_executed": total_batches,
            "success_rate": self._metrics["batches_success"] / max(total_batches, 1),
            "avg_batch_latency_ms": self._metrics["total_latency_ms"] / max(total_batches, 1),
            "pending_requests": len(self._pending_requests)
        }
    
    async def close(self):
        """Cleanup resources"""
        if self._session:
            await self._session.close()

2. Concurrency Controller với Token Bucket

import time
import asyncio
from typing import Optional
from dataclasses import dataclass

@dataclass
class TokenBucketConfig:
    rate: float = 100.0  # requests per second
    capacity: int = 200
    refill_interval: float = 1.0

class TokenBucket:
    """Token bucket algorithm cho rate limiting"""
    
    def __init__(self, config: TokenBucketConfig):
        self.rate = config.rate
        self.capacity = config.capacity
        self.tokens = float(config.capacity)
        self.last_refill = time.monotonic()
        self._lock = asyncio.Lock()
        
    async def acquire(self, tokens: int = 1) -> float:
        """Acquire tokens, return wait time if throttled"""
        async with self._lock:
            await self._refill()
            
            if self.tokens >= tokens:
                self.tokens -= tokens
                return 0.0
                
            # Calculate wait time
            tokens_needed = tokens - self.tokens
            wait_time = tokens_needed / self.rate
            return wait_time
            
    async def _refill(self):
        """Refill tokens based on elapsed time"""
        now = time.monotonic()
        elapsed = now - self.last_refill
        
        new_tokens = elapsed * self.rate
        self.tokens = min(self.capacity, self.tokens + new_tokens)
        self.last_refill = now

class ConcurrencyLimiter:
    """
    Semaphore-based concurrency control với graceful degradation
    """
    
    def __init__(self, max_concurrent: int = 10, max_queue_size: int = 1000):
        self._semaphore = asyncio.Semaphore(max_concurrent)
        self._queue_semaphore = asyncio.Semaphore(max_queue_size)
        self._active_count = 0
        self._lock = asyncio.Lock()
        self._waiters = 0
        
    async def acquire(self, timeout: Optional[float] = 30.0):
        """Acquire concurrency slot với optional timeout"""
        try:
            await asyncio.wait_for(
                self._queue_semaphore.acquire(),
                timeout=timeout
            )
        except asyncio.TimeoutError:
            raise TimeoutError(f"Queue full, timeout after {timeout}s")
            
        self._waiters += 1
        try:
            await self._semaphore.acquire()
            self._waiters -= 1
            async with self._lock:
                self._active_count += 1
        except:
            self._waiters -= 1
            self._queue_semaphore.release()
            raise
            
    def release(self):
        """Release concurrency slot"""
        self._semaphore.release()
        self._queue_semaphore.release()
        async with self._lock:
            self._active_count -= 1
            
    def get_stats(self) -> dict:
        return {
            "active_requests": self._active_count,
            "queued_requests": self._waiters,
            "available_slots": self._semaphore._value
        }

class AdaptiveConcurrencyController:
    """
    Adaptive controller điều chỉnh concurrency dựa trên response time
    """
    
    def __init__(self):
        self.limiter = ConcurrencyLimiter(max_concurrent=10)
        self.token_bucket = TokenBucket(TokenBucketConfig())
        self._latency_history: list = []
        self._window_size = 100
        self._target_latency_ms = 100.0
        self._current_limit = 10
        
    async def execute(self, coro):
        """Execute coroutine với adaptive concurrency"""
        wait_time = await self.token_bucket.acquire()
        if wait_time > 0:
            await asyncio.sleep(wait_time)
            
        start = time.perf_counter()
        try:
            result = await self.limiter.acquire(timeout=30.0)
            async with self.limiter._lock:
                if asyncio.current_task() is None:
                    pass
                    
            try:
                return await coro
            finally:
                self.limiter.release()
        finally:
            latency_ms = (time.perf_counter() - start) * 1000
            self._update_latency(latency_ms)
            
    def _update_latency(self, latency_ms: float):
        """Cập nhật latency history và adjust concurrency"""
        self._latency_history.append(latency_ms)
        if len(self._latency_history) > self._window_size:
            self._latency_history.pop(0)
            
        avg_latency = sum(self._latency_history) / len(self._latency_history)
        
        # Adaptive adjustment
        if avg_latency > self._target_latency_ms * 1.5:
            # Latency cao, giảm concurrency
            new_limit = max(1, int(self._current_limit * 0.8))
            if new_limit != self._current_limit:
                self._adjust_limit(new_limit)
        elif avg_latency < self._target_latency_ms * 0.7:
            # Latency thấp, tăng concurrency
            new_limit = min(50, int(self._current_limit * 1.2))
            if new_limit != self._current_limit:
                self._adjust_limit(new_limit)
                
    def _adjust_limit(self, new_limit: int):
        """Điều chỉnh concurrency limit"""
        self._current_limit = new_limit
        # Recreate semaphore với new limit
        self.limiter = ConcurrencyLimiter(max_concurrent=new_limit)

3. Benchmark Results và Cost Analysis

import asyncio
import time
import statistics
from typing import List, Tuple

async def benchmark_batch_performance():
    """
    Benchmark batch request performance vs sequential requests
    Test environment: 1000 requests, varying batch sizes
    """
    config = BatchConfig(
        max_batch_size=50,
        max_wait_ms=100,
        max_concurrent_batches=5
    )
    
    client = BatchMCPClient(config)
    await client.initialize()
    
    # Generate test requests
    test_requests = [
        BatchRequest(
            id=f"req_{i}",
            messages=[
                {"role": "system", "content": "You are a helpful assistant."},
                {"role": "user", "content": f"Process task {i}"}
            ],
            model="deepseek-v3.2"
        )
        for i in range(1000)
    ]
    
    results = []
    
    # Scenario 1: Sequential requests
    print("=== Sequential Request Benchmark ===")
    start = time.perf_counter()
    for req in test_requests[:100]:  # Test 100 for comparison
        response = await client.add_request(req)
        results.append(response)
    seq_time = time.perf_counter() - start
    print(f"Sequential (100 requests): {seq_time:.2f}s")
    print(f"Avg per request: {seq_time/100*1000:.1f}ms")
    
    # Scenario 2: Batch requests (full 1000)
    print("\n=== Batch Request Benchmark ===")
    results.clear()
    start = time.perf_counter()
    
    tasks = [client.add_request(req) for req in test_requests]
    await asyncio.gather(*tasks)
    
    # Wait for all batches to complete
    await asyncio.sleep(2)  # Allow batch processing to complete
    batch_time = time.perf_counter() - start
    
    print(f"Batch (1000 requests): {batch_time:.2f}s")
    print(f"Avg per request: {batch_time/1000*1000:.2f}ms")
    print(f"Throughput: {1000/batch_time:.1f} req/s")
    
    # Cost comparison
    print("\n=== Cost Analysis (1000 requests) ===")
    avg_tokens_per_request = 500  # avg input + output
    
    sequential_cost = (1000 * avg_tokens_per_request / 1_000_000) * 8  # GPT-4.1
    batch_cost_deepseek = (1000 * avg_tokens_per_request / 1_000_000) * 0.42
    batch_cost_gpt4 = (1000 * avg_tokens_per_request / 1_000_000) * 8
    batch_cost_claude = (1000 * avg_tokens_per_request / 1_000_000) * 15
    
    print(f"Sequential (GPT-4.1 @ $8/MTok): ${sequential_cost:.2f}")
    print(f"Batch (DeepSeek V3.2 @ $0.42/MTok): ${batch_cost_deepseek:.4f}")
    print(f"Batch (GPT-4.1 @ $8/MTok): ${batch_cost_gpt4:.2f}")
    print(f"Batch (Claude Sonnet 4.5 @ $15/MTok): ${batch_cost_claude:.2f}")
    print(f"Savings with DeepSeek V3.2: {((batch_cost_gpt4 - batch_cost_deepseek)/batch_cost_gpt4)*100:.1f}%")
    
    # Performance metrics
    metrics = client.get_metrics()
    print(f"\n=== System Metrics ===")
    print(f"Total batches: {metrics['batches_executed']}")
    print(f"Success rate: {metrics['success_rate']*100:.1f}%")
    print(f"Avg batch latency: {metrics['avg_batch_latency_ms']:.1f}ms")
    
    await client.close()
    
    return {
        "sequential_time": seq_time,
        "batch_time": batch_time,
        "speedup": seq_time / batch_time * 10,  # 100 vs 1000
        "cost_savings": batch_cost_gpt4 - batch_cost_deepseek
    }

Run benchmark

if __name__ == "__main__": results = asyncio.run(benchmark_batch_performance()) """ Expected Results: - Sequential: ~12-15s for 100 requests - Batch: ~3-5s for 1000 requests - Speedup: 20-40x improvement - Cost savings: 95%+ with DeepSeek V3.2 """

Lỗi thường gặp và cách khắc phục

1. Lỗi 429 Too Many Requests

Mô tả: Bị rate limit khi gửi quá nhiều request đồng thời, đặc biệt khi HolySheep áp dụng limit theo tier.

# ❌ WRONG: Không handle rate limit, crash immediately
async def send_requests():
    for req in requests:
        response = await client.post(req)  # Rapid fire
    return results

✅ CORRECT: Implement exponential backoff với retry logic

async