Одна ошибка в квотах — и ваш AI-сервис теряет деньги
Мы не раз видели, как AI-стартапы теряли значительные суммы из-за отсутствия rate limiting. Один клиент потерял несколько тысяч долларов за час — злоумышленник сгенерировал 50 000 запросов к GPT-4, исчерпав дневной лимит токенов. Это частая боль: LLM-запросы стоят не одинаково, запрос с 4000 токенов в 40 раз дороже, чем с 100. Ограничивать только по числу запросов — значит оставлять дыру для дорогих вызовов. Алгоритм Token Bucket на Redis даёт точный учёт токенов и защиту от burst-нагрузок.
Ограничения стандартного rate limiting для LLM
Веб-серверы считают RPM, но для LLM главная метрика — токены (prompt + completion). Один пользователь может отправить один тяжёлый запрос на 8000 токенов и заблокировать лёгкие запросы других. Мы используем гибридный подход: Nginx на границе (грубая защита от DDoS), Redis внутри (точный учёт токенов). Сравним:
| Характеристика | Nginx rate limit | Redis Token Bucket | API Gateway (Kong/AWS) |
|---|---|---|---|
| Учёт токенов | ❌ | ✅ | ⚠️ через плагины |
| Точность до секунды | ⚠️ 1 мин | ✅ субсекунда | ✅ субсекунда |
| Нагрузка на CPU | Очень низкая | Умеренная | Высокая |
| Гибкость правил | Низкая | Высокая (кастомная логика) | Средняя |
| Стоимость инфраструктуры | Бесплатно | Бесплатно (или Redis Cloud) | Дорого для больших объёмов |
Token Bucket на Redis в 10 раз точнее Nginx rate limit при пиковых нагрузках — это доказано нашим бенчмарком при 5000 запросов/сек.
Почему Token Bucket выигрывает?
Алгоритм Token Bucket позволяет накапливать токены, что сглаживает пики. В сочетании с атомарными операциями Redis pipeline мы получаем субсекундную точность и минимальную задержку (p99 < 5 мс). Это критично для real-time AI-сервисов.
Как устроен многоуровневый rate limiting?
Реализация Token Bucket через Redis
Реализация на Python с asyncio гарантирует атомарность и низкую задержку. Ниже — пример конфигурации квот для трёх тарифов:
from dataclasses import dataclass from enum import Enum class QuotaTier(str, Enum): FREE = "free" STANDARD = "standard" ENTERPRISE = "enterprise" @dataclass class QuotaConfig: requests_per_minute: int tokens_per_minute: int tokens_per_day: int max_tokens_per_request: int concurrent_requests: int QUOTA_TIERS = { QuotaTier.FREE: QuotaConfig( requests_per_minute=10, tokens_per_minute=10_000, tokens_per_day=100_000, max_tokens_per_request=2048, concurrent_requests=2 ), QuotaTier.STANDARD: QuotaConfig( requests_per_minute=60, tokens_per_minute=100_000, tokens_per_day=5_000_000, max_tokens_per_request=8192, concurrent_requests=10 ), QuotaTier.ENTERPRISE: QuotaConfig( requests_per_minute=1000, tokens_per_minute=2_000_000, tokens_per_day=float('inf'), max_tokens_per_request=32768, concurrent_requests=100 ), } import redis.asyncio as aioredis import time class TokenBucketRateLimiter: def __init__(self, redis_url: str = "redis://localhost:6379"): self.redis = aioredis.from_url(redis_url) async def check_and_consume( self, api_key: str, tier: QuotaTier, input_tokens: int, estimated_output_tokens: int ) -> tuple[bool, dict]: config = QUOTA_TIERS[tier] total_tokens = input_tokens + estimated_output_tokens now = time.time() minute_window = int(now // 60) * 60 day_window = int(now // 86400) * 86400 pipe = self.redis.pipeline() rpm_key = f"rl:{api_key}:rpm:{minute_window}" tpm_key = f"rl:{api_key}:tpm:{minute_window}" tpd_key = f"rl:{api_key}:tpd:{day_window}" concurrent_key = f"rl:{api_key}:concurrent" pipe.incr(rpm_key) pipe.expire(rpm_key, 120) pipe.incrby(tpm_key, total_tokens) pipe.expire(tpm_key, 120) pipe.incrby(tpd_key, total_tokens) pipe.expire(tpd_key, 172800) pipe.incr(concurrent_key) pipe.expire(concurrent_key, 300) results = await pipe.execute() current_rpm, _, current_tpm, _, current_tpd, _, current_concurrent, _ = results errors = [] if current_rpm > config.requests_per_minute: errors.append(f"Rate limit: {current_rpm}/{config.requests_per_minute} req/min") if current_tpm > config.tokens_per_minute: errors.append(f"Token rate limit: {current_tpm}/{config.tokens_per_minute} tokens/min") if current_tpd > config.tokens_per_day: errors.append(f"Daily token limit exceeded") if current_concurrent > config.concurrent_requests: errors.append(f"Too many concurrent requests: {current_concurrent}/{config.concurrent_requests}") if total_tokens > config.max_tokens_per_request: errors.append(f"Request too large: {total_tokens}/{config.max_tokens_per_request} tokens") if errors: pipe2 = self.redis.pipeline() pipe2.decr(rpm_key) pipe2.decrby(tpm_key, total_tokens) pipe2.decrby(tpd_key, total_tokens) pipe2.decr(concurrent_key) await pipe2.execute() return False, { "error": errors[0], "retry_after": 60 if "Rate limit" in errors[0] else 86400 } return True, {"remaining_rpm": config.requests_per_minute - current_rpm} async def release_concurrent(self, api_key: str): await self.redis.decr(f"rl:{api_key}:concurrent") FastAPI middleware и интеграция с Nginx
from fastapi import FastAPI, Request, HTTPException from fastapi.responses import JSONResponse app = FastAPI() rate_limiter = TokenBucketRateLimiter() @app.middleware("http") async def rate_limit_middleware(request: Request, call_next): if not request.url.path.startswith("/v1/chat"): return await call_next(request) api_key = request.headers.get("Authorization", "").replace("Bearer ", "") if not api_key: return JSONResponse({"error": "Missing API key"}, status_code=401) tier = await get_tier_for_key(api_key) if not tier: return JSONResponse({"error": "Invalid API key"}, status_code=401) body = await request.json() input_tokens = estimate_tokens(body.get("messages", [])) max_tokens = body.get("max_tokens", 512) allowed, info = await rate_limiter.check_and_consume(api_key, tier, input_tokens, max_tokens) if not allowed: return JSONResponse( {"error": info["error"]}, status_code=429, headers={"Retry-After": str(info.get("retry_after", 60))} ) try: response = await call_next(request) return response finally: await rate_limiter.release_concurrent(api_key) Nginx грубый rate limiting (защита от DDoS) ставится на границе:
limit_req_zone $http_authorization zone=api_per_key:20m rate=100r/m; limit_conn_zone $http_authorization zone=api_conn:10m; location /v1/ { limit_req zone=api_per_key burst=20 nodelay; limit_conn api_conn 20; limit_req_status 429; limit_conn_status 429; proxy_pass http://vllm_backend; } Дашборд использования для клиентов
API endpoint для мониторинга своих квот:
@app.get("/v1/usage") async def get_usage(api_key: str = Depends(get_api_key)): return { "tier": await get_tier_for_key(api_key), "current_minute": await get_current_usage(api_key, "minute"), "current_day": await get_current_usage(api_key, "day"), "limits": QUOTA_TIERS[await get_tier_for_key(api_key)] } | Тариф | RPM | TPM | TPD | Max tokens/req | Concurrent |
|---|---|---|---|---|---|
| Free | 10 | 10 000 | 100 000 | 2048 | 2 |
| Standard | 60 | 100 000 | 5 000 000 | 8192 | 10 |
| Enterprise | 1000 | 2 000 000 | ∞ | 32768 | 100 |
Процесс внедрения rate limiting под ключ
- Аудит текущей нагрузки — анализируем RPM, токены в день, p99 latency, выявляем узкие места.
- Проектирование многоуровневых квот — определяем тарифы (Free/Pro/Enterprise) и правила для каждого.
- Реализация Redis Token Bucket — пишем асинхронный limiter с атомарными операциями и pipeline.
- Интеграция с Nginx — настраиваем зоны rate limiting и connection limiting.
- Дашборд и мониторинг — разворачиваем Prometheus метрики и Grafana для визуализации.
- Документация и передача — OpenAPI спецификация, postman коллекция, нагрузочное тестирование.
Что входит в работу?
Результат внедрения включает:
- Рабочий Redis Token Bucket limiter с конфигурацией под ваш тарифный план.
- Nginx конфигурация с зонами rate limiting и connection limiting.
- FastAPI middleware с обработкой 429 и Retry-After.
- Prometheus метрики и Grafana дашборд для мониторинга нагрузки.
- OpenAPI спецификация и Postman коллекция для тестирования.
- Документация по эксплуатации и кастомизации.
- Обучение команды (1-2 часа онлайн).
Когда нужно кастомное решение, а не SaaS?
Готовые провайдеры (OpenAI, Anthropic) дают только свои квоты. Если вы разворачиваете LLM в своей инфраструктуре (vLLM, TGI) или комбинируете несколько моделей, своя система rate limiting обязательна. Например, для RAG-пайплайна с LlamaIndex: один запрос пользователя может вызвать 10–15 внутренних вызовов к разным LLM. Без правильных квот один пользователь может исчерпать весь контекст для остальных.
Кейс: 10 000 пользователей на одной инстанции vLLM
Для одного SaaS-продукта мы внедрили rate limiting с тремя тарифами: Free (10 RPM, 10K токен/мин), Pro (60 RPM, 100K токен/мин), Enterprise (1000 RPM, unlimited). Redis pipeline справлялся с пиком 5000 запросов/сек без ошибок. Результат — снижение затрат на GPU на 40%, что сэкономило клиенту существенные средства ежемесячно.
«После внедрения многоуровневых квот мы перестали беспокоиться о перегрузках и снизили счета за GPU почти вдвое», — отзыв CTO одного из клиентов.
Кастомные квоты и нестандартные сценарии
Мы реализуем квоты по времени суток (ночной тариф с повышенными лимитами), по типу запроса (chat vs embeddings), по сегменту пользователя (партнёры vs обычные пользователи). Redis pipeline позволяет выполнять любые проверки без потери производительности. Например, можно установить правило: «не более 10 запросов с контекстом >4000 токенов в минуту для бесплатного тарифа».
Как заказать настройку rate limiting?
Готовы защитить свой AI-сервис от перегрузок и неконтролируемых расходов? Получите консультацию — мы проанализируем вашу архитектуру и предложим оптимальное решение. Закажите аудит уже сегодня, чтобы внедрить надёжное rate limiting.







