中文翻译暂不可用,显示俄语原文。

Что такое 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
IPIP-адрес клиента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)

Порядок проверки (сверху вниз):

  1. Global — самый строгий, защищает инфраструктуру
  2. API key — защищает от превышения квоты провайдера
  3. User — fair use между пользователями
  4. IP — защита от атак

Если любой уровень возвращает denyHTTP 429 с заголовком Retry-After.

Термин: Middleware — программный слой, перехватывающий запросы до их обработки.


5. Распределённый rate limiting с Redis

В кластерной среде (несколько экземпляров агентов) rate limiting должен быть централизованным. Redis — идеальный кандидат благодаря:

  • Атомарным операциям (INCR, Lua)
  • Встроенным TTL (expire)
  • Высокой производительности (in-memory)

Проблемы и решения

ПроблемаРешение
Race condition при параллельных запросахLua-скрипты или Redis transactions (MULTI/EXEC)
Потеря данных при падении RedisRedis 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 или собственная реализация.

Шаги:

  1. Запустите Redis в Docker: docker run -p 6379:6379 redis
  2. Напишите класс RateLimiter с sliding window для user и token bucket для global.
  3. Создайте FastAPI endpoint /agent/query, который принимает user_id и api_key.
  4. Добавьте декоратор или middleware, проверяющий лимиты перед вызовом агента.
  5. При превышении возвращайте 429 с Retry-After.
  6. Напишите тест: отправьте 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?

Навигация