부하 분산과 다중 키 전략
여러 API 키와 계정에 라운드 로빈 및 가중치 기반 부하 분산을 구현해 속도 제한 여유를 늘리고 p99 지연 시간 급증을 줄입니다.
부하 분산과 다중 키 전략은(는) CoddyKit의 무료 AI Engineering Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Engineering Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Why One API Key Is Not Enough
A single OpenAI API key has a fixed rate limit measured in requests per minute (RPM) and tokens per minute (TPM). At Tier 1, GPT-4o allows 500 RPM and 30,000 TPM. For a production application with hundreds of concurrent users, a single key will hit these limits constantly. Multiple API keys multiply your available headroom proportionally.
Creating Multiple API Keys
You can create multiple API keys within a single OpenAI organization, or create multiple OpenAI accounts (each billed separately). Store each key in your environment configuration and treat them as a pool. Keep keys in a secrets manager like AWS Secrets Manager or HashiCorp Vault rather than in your source code or .env files committed to version control.
import os
API_KEYS = [
os.environ['OPENAI_KEY_1'],
os.environ['OPENAI_KEY_2'],
os.environ['OPENAI_KEY_3'],
os.environ['OPENAI_KEY_4'],
]
# Total effective RPM = 500 * 4 = 2000 RPM
# Total effective TPM = 30000 * 4 = 120000 TPMRound-Robin Load Balancing
Round-robin distributes requests evenly across all keys by cycling through them in order. It is simple to implement and ensures each key handles roughly the same load over time. Use a thread-safe counter or an atomic integer to avoid two concurrent requests picking the same key simultaneously. Round-robin works well when all keys have identical rate limits.
import itertools
import threading
from openai import OpenAI
class RoundRobinPool:
def __init__(self, keys: list):
self._clients = [OpenAI(api_key=k) for k in keys]
self._cycle = itertools.cycle(range(len(self._clients)))
self._lock = threading.Lock()
def get_client(self) -> OpenAI:
with self._lock:
idx = next(self._cycle)
return self._clients[idx]
pool = RoundRobinPool(API_KEYS)
client = pool.get_client()Weighted Load Balancing
Weighted load balancing assigns higher-tier keys (with higher rate limits) a larger share of traffic proportional to their capacity. If key A is Tier 3 (10,000 RPM) and key B is Tier 1 (500 RPM), key A should receive ~95% of requests. Weighted balancing prevents lower-tier keys from becoming bottlenecks when mixed with higher-tier ones.
import random
class WeightedPool:
def __init__(self, key_configs: list):
# key_configs = [{'key': '...', 'weight': 10}, ...]
self._clients = [OpenAI(api_key=c['key']) for c in key_configs]
self._weights = [c['weight'] for c in key_configs]
def get_client(self) -> OpenAI:
return random.choices(self._clients, weights=self._weights, k=1)[0]
pool = WeightedPool([
{'key': os.environ['OPENAI_KEY_TIER3'], 'weight': 20},
{'key': os.environ['OPENAI_KEY_TIER1'], 'weight': 1},
])Tracking Per-Key Rate Limit State
The OpenAI API returns rate limit headers with every response: x-ratelimit-remaining-requests and x-ratelimit-remaining-tokens. Track these headers per key to know which keys are close to exhaustion. When a key reports fewer than 10 remaining requests in the current minute, temporarily route traffic away from it to prevent 429 errors before they occur.
class SmartPool:
def __init__(self, keys: list):
self._clients = [OpenAI(api_key=k) for k in keys]
self._remaining = {i: 500 for i in range(len(keys))} # initial RPM
def get_best_client(self):
# Pick key with most remaining capacity
best_idx = max(self._remaining, key=lambda i: self._remaining[i])
return self._clients[best_idx], best_idx
def update_remaining(self, idx: int, response_headers: dict):
remaining = int(response_headers.get('x-ratelimit-remaining-requests', 0))
self._remaining[idx] = remainingHandling 429 Rate Limit Errors
When a key returns a 429 error, immediately retire that key from the pool for the duration specified in the Retry-After header (typically 60 seconds). Mark it as cooling down and route all traffic to remaining keys. After the cool-down window expires, restore the key to the pool. This prevents cascading failures where retries on the same key make the situation worse.
import time
from openai import RateLimitError
class CooldownPool:
def __init__(self, keys: list):
self._clients = [(OpenAI(api_key=k), None) for k in keys] # (client, cooldown_until)
def get_available_clients(self):
now = time.time()
return [
(i, c) for i, (c, until) in enumerate(self._clients)
if until is None or until <= now
]
def mark_cooling(self, idx: int, retry_after: int = 60):
client, _ = self._clients[idx]
self._clients[idx] = (client, time.time() + retry_after)
print(f'Key {idx} cooling down for {retry_after}s')Using OpenRouter as a Multiplexer
OpenRouter is a proxy service that exposes hundreds of models through a single OpenAI-compatible API endpoint. By routing through OpenRouter, you automatically get load balancing across multiple underlying provider accounts, fallback to alternative providers, and access to open-source models as backups. The cost markup is small for the operational simplicity it provides.
from openai import OpenAI
# OpenRouter uses the same OpenAI SDK interface
client = OpenAI(
api_key=os.environ['OPENROUTER_API_KEY'],
base_url='https://openrouter.ai/api/v1'
)
response = client.chat.completions.create(
model='openai/gpt-4o', # OpenRouter model name format
messages=[{'role': 'user', 'content': prompt}]
)
# Automatic failover if OpenAI is downMonitoring Key Health with Metrics
Track per-key metrics including requests sent, 429 errors received, and cool-down time in the last hour. A key with a high 429 rate needs either traffic reduction or a tier upgrade. Expose these metrics on a /metrics endpoint in Prometheus format so your monitoring system can alert when any key is consistently hitting limits.
from dataclasses import dataclass, field
from collections import defaultdict
@dataclass
class KeyMetrics:
requests_sent: int = 0
rate_limit_errors: int = 0
total_tokens_used: int = 0
cooldown_count: int = 0
class MetricPool:
def __init__(self, keys: list):
self._clients = [OpenAI(api_key=k) for k in keys]
self._metrics = [KeyMetrics() for _ in keys]
def report(self):
for i, m in enumerate(self._metrics):
error_rate = m.rate_limit_errors / max(m.requests_sent, 1)
print(f'Key {i}: {m.requests_sent} req, {error_rate:.1%} 429 rate')Geographic Key Distribution
If your users are spread globally, consider maintaining separate API keys per geographic region and routing requests to the key closest to the user. Reduced network round-trip time improves TTFT. Deploy a lightweight load balancer in each region (AWS Lambda@Edge or Cloudflare Worker) that selects the appropriate key and proxies the request, shielding your keys from client exposure.
REGIONAL_KEYS = {
'us-east': os.environ['OPENAI_KEY_US_EAST'],
'eu-west': os.environ['OPENAI_KEY_EU_WEST'],
'ap-southeast': os.environ['OPENAI_KEY_AP'],
}
def get_key_for_region(user_region: str) -> str:
# Default to us-east if region unknown
return REGIONAL_KEYS.get(user_region, REGIONAL_KEYS['us-east'])Testing Your Load Balancer
Write a load test that fires 100 concurrent requests through your balancing pool and measures distribution, error rates, and latency percentiles. Verify that no single key handles more than its proportional share and that 429 errors are below 0.1%. Use asyncio.gather or a tool like Locust to simulate the concurrent load your production system will actually experience.
import asyncio
import time
async def load_test(pool, concurrency=100, total=1000):
sem = asyncio.Semaphore(concurrency)
results = []
async def one_request():
async with sem:
client = pool.get_client()
start = time.perf_counter()
try:
await client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': 'Ping'}],
max_tokens=5
)
results.append(('ok', time.perf_counter() - start))
except Exception as e:
results.append(('error', str(e)))
await asyncio.gather(*[one_request() for _ in range(total)])
ok = [r for r in results if r[0] == 'ok']
print(f'Success rate: {len(ok)/total:.1%}')
return resultsChoosing the Right Balancing Strategy
Match your balancing strategy to your rate limit structure. Use round-robin when all keys have identical tier limits and traffic is evenly distributed. Use weighted balancing when keys have different tier limits. Use health-aware routing (skipping keys close to exhaustion) when you need to minimize 429 errors under burst traffic. For most production systems, health-aware routing with exponential backoff gives the best balance of simplicity and resilience.
# Strategy selection guide:
# Scenario A: 4 keys all Tier 2 (same limits)
# -> Round-robin: simple, even distribution
#
# Scenario B: 1 Tier 3 key + 3 Tier 1 keys
# -> Weighted: Tier 3 gets 10x weight
#
# Scenario C: Variable traffic with burst periods
# -> Health-aware: track remaining headers, skip near-limit keys
#
# Scenario D: Multi-region, latency-sensitive
# -> Geographic: regional keys, route by user locationQuick Check
Test your understanding of load balancing strategies for LLM APIs.
Lesson Recap
In this lesson you learned: round-robin and weighted balancing distribute traffic across multiple API keys to multiply rate limit headroom, cool-down tracking prevents cascading 429 errors by temporarily removing throttled keys, and OpenRouter provides a managed multiplexing option with automatic fallback. Next up we implement fallback providers and circuit breakers.
자주 묻는 질문
“부하 분산과 다중 키 전략” 강의는 무료인가요?
네 — “부하 분산과 다중 키 전략” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Engineering Academy 강의 전체를 잠금 해제할 수 있습니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“부하 분산과 다중 키 전략”에서 뭘 배우나요?
여러 API 키와 계정에 라운드 로빈 및 가중치 기반 부하 분산을 구현해 속도 제한 여유를 늘리고 p99 지연 시간 급증을 줄입니다. 브라우저에서 직접 실행하는 실습 코드로 AI Engineering Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
AI Engineering Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 AI Engineering Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“부하 분산과 다중 키 전략” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 AI Engineering Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 AI Engineering Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.