中文翻译暂不可用,显示俄语原文。
Что такое rate limiting на разных уровнях (user, API key, IP, global) и как реализовать?
Краткий тезис
Rate limiting (ограничение скорости) — механизм контроля частоты запросов к системе, предотвращающий перегрузку, абуз и обеспечивающий fair use (справедливое распределение ресурсов). В контексте RAG|Agentic RAG агенты могут генерировать десятки параллельных вызовов к LLM, векторным БД и внешним API, поэтому многоуровневое rate limiting критично. Реализация обычно строится на Redis с алгоритмами sliding window counter (скользящее окно) и token bucket (токен-бакет), каждый уровень имеет свой лимит: user (100 req/min), API key (1000 req/min), IP (50 req/min), global (N req/s).
1. Зачем нужно rate limiting в Agentic RAG
Агентные системы (ReAct, Plan-and-Execute) делают множество последовательных и параллельных вызовов:
- LLM (chat completions)
- Embedding API
- Векторные БД (поиск, запись)
- Внешние инструменты (веб-поиск, калькуляторы)
Без rate limiting один пользователь или сбойный агент может:
- Исчерпать квоту API-провайдера (например, OpenAI 5000 RPM)
- Вызвать каскадный отказ БД (connection pool exhaustion)
- Создать несправедливое распределение ресурсов между пользователями
Термин: Fair use — принцип, при котором ресурсы распределяются пропорционально, а не захватываются одним клиентом.
2. Уровни rate limiting
| Уровень | Объект ограничения | Типичный лимит | Пример в Agentic RAG |
|---|---|---|---|
| User | Конкретный пользователь (по user_id) | 100 req/min | Агент одного пользователя делает 100 вызовов LLM в минуту |
| API key | Ключ доступа к сервису (например, OpenAI key) | 1000 req/min | Все агенты, использующие один API key, суммарно 1000 RPM |
| IP | IP-адрес клиента | 50 req/min | Защита от DDoS или ботов с одного IP |
| Global | Весь сервис (все пользователи вместе) | 5000 req/s | Предотвращение перегрузки инфраструктуры |
Важно уровни могут комбинироваться — запрос проверяется последовательно: сначала global, потом API key, потом user, потом IP. Если хотя бы один лимит превышен — запрос отклоняется (HTTP 429 Too Many Requests).
3. Алгоритмы rate limiting
3.1 Sliding Window Counter (скользящее окно)
Самый популярный алгоритм для распределённых систем. Разбивает время на фиксированные окна (например, 1 минута) и хранит счётчик запросов в текущем окне + взвешенную долю предыдущего окна.
Формула
current_count = count_current_window + count_previous_window * (time_since_window_start / window_size)
if current_count >= limit → reject
Реализация на Redis (псевдокод):
import time
import redis
r = redis.Redis()
def sliding_window_check(key: str, limit: int, window_sec: int = 60) -> bool:
now = time.time()
window_key = f"rate:{key}:{int(now // window_sec)}"
prev_window_key = f"rate:{key}:{int(now // window_sec) - 1}"
# INCR текущего окна (атомарно)
current = r.incr(window_key)
r.expire(window_key, window_sec * 2) # TTL на всякий случай
# Получаем счётчик предыдущего окна
prev = int(r.get(prev_window_key) or 0)
# Взвешенная доля предыдущего окна
elapsed = now % window_sec
weight = (window_sec - elapsed) / window_sec
estimated = current + prev * weight
return estimated <= limit
Термин: Атомарная операция — операция, выполняемая за один шаг (в Redis INCR — атомарный инкремент).
3.2 Token Bucket (токен-бакет)
Используется для global уровня и для burst-трафика. Бакет содержит N токенов, каждый запрос забирает один токен. Токены восполняются с постоянной скоростью (rate).
Параметры
- capacity — максимальное количество токенов (burst)
refill_rate— сколько токенов добавляется в секунду
Реализация на Redis с Lua-скриптом (атомарно):
-- KEYS[1] = bucket key
-- ARGV[1] = capacity
-- ARGV[2] = refill_rate
-- ARGV[3] = now (timestamp)
local bucket = redis.call('HMGET', KEYS[1], 'tokens', 'last_refill')
local tokens = tonumber(bucket[1]) or tonumber(ARGV[1])
local last_refill = tonumber(bucket[2]) or tonumber(ARGV[3])
local elapsed = tonumber(ARGV[3]) - last_refill
tokens = math.min(tokens + elapsed * tonumber(ARGV[2]), tonumber(ARGV[1]))
if tokens >= 1 then
redis.call('HMSET', KEYS[1], 'tokens', tokens - 1, 'last_refill', ARGV[3])
return 1 -- allow
else
return 0 -- deny
end
Термин: Lua-скрипт — скрипт, выполняемый на стороне Redis атомарно, без race conditions.
3.3 Leaky Bucket (дырявое ведро)
Альтернатива token bucket, где запросы ставятся в очередь и обрабатываются с постоянной скоростью. Избыточные запросы отбрасываются. Меньше подходит для распределённых систем из-за необходимости очереди.
4. Многоуровневая архитектура rate limiting
В Agentic RAG rate limiting обычно реализуется как middleware (промежуточный слой) между агентом и внешними сервисами.
[Агент] → [Rate Limiter Middleware] → [LLM API / Vector DB / Tools]
│
├─ Global: Token Bucket (Redis)
├─ API Key: Sliding Window (Redis)
├─ User: Sliding Window (Redis)
└─ IP: Sliding Window (Redis)
Порядок проверки (сверху вниз):
- Global — самый строгий, защищает инфраструктуру
- API key — защищает от превышения квоты провайдера
- User — fair use между пользователями
- IP — защита от атак
Если любой уровень возвращает deny → HTTP 429 с заголовком Retry-After.
Термин: Middleware — программный слой, перехватывающий запросы до их обработки.
5. Распределённый rate limiting с Redis
В кластерной среде (несколько экземпляров агентов) rate limiting должен быть централизованным. Redis — идеальный кандидат благодаря:
- Атомарным операциям (INCR, Lua)
- Встроенным TTL (expire)
- Высокой производительности (in-memory)
Проблемы и решения
| Проблема | Решение |
|---|---|
| Race condition при параллельных запросах | Lua-скрипты или Redis transactions (MULTI/EXEC) |
| Потеря данных при падении Redis | Redis Sentinel/Cluster + персистентность (RDB/AOF) |
| Задержка при большом количестве ключей | Sharding по ключам (user_id, IP) |
| Несинхронизированные часы между нодами | Использовать Redis TIME или передавать timestamp от клиента |
Пример интеграции с FastAPI
from fastapi import FastAPI, Request, HTTPException
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(429, _rate_limit_exceeded_handler)
@app.get("/agent/query")
@limiter.limit("50/minute") # IP-based
async def agent_query(request: Request):
# ... логика агента
pass
6. Rate limiting для Agentic RAG: специфика
Агенты могут делать цепочки вызовов (chain of calls). Например, один запрос пользователя может породить 5 вызовов LLM + 3 поиска в БД. Нужно учитывать:
- Cost-aware limiting — ограничивать не только по числу запросов, но и по токенам (input + output)
- Tool-specific limits — разные лимиты для разных инструментов (LLM — 100 RPM, веб-поиск — 10 RPM)
- Adaptive limiting — динамически снижать лимиты при высокой нагрузке (circuit breaker)
Термин: Circuit breaker — паттерн, при котором при превышении порога ошибок вызовы временно блокируются.
7. Пример реализации на Python + Redis
import redis
import time
from typing import Optional
class RateLimiter:
def __init__(self, redis_client: redis.Redis):
self.redis = redis_client
def check_sliding_window(self, key: str, limit: int, window: int = 60) -> bool:
"""Sliding window counter"""
now = time.time()
current_window = int(now // window)
prev_window = current_window - 1
# Текущее окно
current_key = f"sw:{key}:{current_window}"
current_count = self.redis.incr(current_key)
self.redis.expire(current_key, window * 2)
# Предыдущее окно
prev_key = f"sw:{key}:{prev_window}"
prev_count = int(self.redis.get(prev_key) or 0)
# Взвешенная оценка
elapsed = now % window
weight = (window - elapsed) / window
estimated = current_count + prev_count * weight
return estimated <= limit
def check_token_bucket(self, key: str, capacity: int, refill_rate: float) -> bool:
"""Token bucket via Lua"""
lua_script = """
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(bucket[1]) or capacity
local last_refill = tonumber(bucket[2]) or now
local elapsed = now - last_refill
tokens = math.min(tokens + elapsed * refill_rate, capacity)
if tokens >= 1 then
redis.call('HMSET', key, 'tokens', tokens - 1, 'last_refill', now)
return 1
else
return 0
end
"""
return bool(self.redis.eval(lua_script, 1, key, capacity, refill_rate, time.time()))
def check_all_levels(self, user_id: str, api_key: str, ip: str) -> bool:
"""Последовательная проверка всех уровней"""
# Global: token bucket, 5000 req/s, burst 10000
if not self.check_token_bucket("global", 10000, 5000):
return False
# API key: 1000 req/min
if not self.check_sliding_window(f"apikey:{api_key}", 1000):
return False
# User: 100 req/min
if not self.check_sliding_window(f"user:{user_id}", 100):
return False
# IP: 50 req/min
if not self.check_sliding_window(f"ip:{ip}", 50):
return False
return True
8. Обработка превышения лимита (HTTP 429)
При отклонении запроса клиент должен получить:
- HTTP статус 429 Too Many Requests
- Заголовок Retry-After (секунды до следующего разрешённого запроса)
- Заголовки RateLimit (лимит, остаток, сброс)
Пример ответа
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1623456789
{
"error": "rate_limit_exceeded",
"message": "User limit exceeded. Retry after 30 seconds."
}
Backoff-стратегии для агента
- Exponential backoff — увеличивать задержку экспоненциально (1s, 2s, 4s, 8s...)
- Jitter — добавлять случайность, чтобы избежать синхронного повторения
- Queue — ставить запросы в очередь и обрабатывать по мере освобождения лимита
9. Мониторинг и алертинг
Rate limiting нужно мониторить:
- Количество отклонённых запросов (по уровням)
- Текущая загрузка лимитов (сколько процентов от лимита используется)
- Latency Redis (чтобы rate limiter сам не стал узким местом)
Инструменты: Prometheus + Grafana, Redis Exporter.
Термин: SLO (Service Level Objective) — целевой уровень обслуживания, например, «не более 1% запросов получают 429».
10. Альтернативы Redis
| Инструмент | Плюсы | Минусы |
|---|---|---|
| Redis | Быстро, атомарно, распределённо | Требует отдельного сервера |
| In-memory (локально) | Просто, без зависимостей | Не работает в кластере, теряется при рестарте |
| PostgreSQL ( advisory locks) | Уже есть в стеке | Медленнее, блокировки |
| Cloud API Gateway (AWS API Gateway, Kong) | Готовое решение, масштабируется | Дорого, vendor lock-in |
Для Agentic RAG Redis — стандарт де-факто.
Пет-проект для закрепления
Задача Реализовать middleware rate limiting для агента, который вызывает OpenAI API и векторную БД.
Инструменты Python, FastAPI, Redis (через docker), slowapi или собственная реализация.
Шаги:
- Запустите Redis в Docker:
docker run -p 6379:6379 redis - Напишите класс
RateLimiterс sliding window для user и token bucket для global. - Создайте FastAPI endpoint
/agent/query, который принимаетuser_idиapi_key. - Добавьте декоратор или middleware, проверяющий лимиты перед вызовом агента.
- При превышении возвращайте 429 с
Retry-After. - Напишите тест: отправьте 110 запросов за минуту от одного user_id — последние 10 должны получить 429.
Ожидаемый результат Работающий сервис, который защищает от перегрузки, с логами отклонённых запросов и заголовками RateLimit.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 249 | Как реализовать retry и fallback в Agentic RAG? |
| 250 | Что такое circuit breaker и как его применить? |
| 247 | Как управлять состоянием агента (state management)? |
| 245 | Архитектура Agentic RAG: общий обзор |
| 236 | Как уменьшить latency RAG-системы? |
| 240 | Как обеспечить отказоустойчивость RAG? |
Навигация
- Предыдущий: 247
- Следующий: 249
- Индекс: 00. Индекс разборов