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:
- High Latency: Mỗi round-trip HTTP tốn 50-200ms overhead
- Rate Limiting: Bị giới hạn request/second từ provider
- Cost Inefficiency: So sánh chi phí: DeepSeek V3.2 chỉ $0.42/MTok vs GPT-4.1 $8/MTok — tiết kiệm 95%
- Resource Waste: CPU cycles cho serialization/deserialization liên tục
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