Реализация API Rate Limiting и Usage Tracking для SaaS-приложения
Без ограничений на количество запросов один агрессивный клиент способен положить инфраструктуру или израсходовать лимиты downstream-сервисов за минуты. Usage Tracking — это не только защита, но и основа для тарификации: без точных данных о потреблении невозможно выставить счёт по модели pay-per-use или проверить корректность тарифных планов.
Слои rate limiting
Ограничения выстраиваются в несколько уровней. На уровне IP — защита от DDoS и скрейперов. На уровне API-ключа или JWT-токена — per-tenant квоты. На уровне endpoint — отдельные лимиты для дорогих операций (экспорт, генерация отчётов, AI-запросы).
Алгоритмы:
| Алгоритм | Характеристика | Применение |
|---|---|---|
| Fixed Window | Простой, но допускает burst на границе окна | Базовые планы |
| Sliding Window Log | Точный, дорогой по памяти | Премиальные endpoints |
| Token Bucket | Позволяет burst в пределах bucket-size | Большинство SaaS API |
| Leaky Bucket | Сглаживает пики, строгий output rate | Интеграции с внешними API |
Для большинства SaaS Token Bucket — оптимальный выбор: клиент может сделать burst из 10 запросов, если копил токены, но не превысит среднюю скорость.
Реализация на уровне middleware
Node.js / Express — библиотека express-rate-limit с Redis-хранилищем через rate-limit-redis:
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
const planLimits = { free: 100, pro: 1000, enterprise: 10000 };
const apiLimiter = rateLimit({
windowMs: 60 * 1000,
limit: (req) => planLimits[req.tenant.plan] ?? 100,
keyGenerator: (req) => `rl:${req.tenant.id}:${req.path}`,
store: new RedisStore({ client: redisClient }),
handler: (req, res) => {
res.status(429).json({
error: 'rate_limit_exceeded',
retryAfter: res.getHeader('Retry-After'),
});
},
standardHeaders: 'draft-7',
legacyHeaders: false,
});
Заголовки RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset по RFC 6585 / draft-7 обязательны — клиенты SDK используют их для back-off.
Python / FastAPI — библиотека slowapi поверх limits:
from slowapi import Limiter
limiter = Limiter(key_func=lambda req: req.state.tenant_id,
storage_uri="redis://localhost:6379")
@app.get("/api/reports")
@limiter.limit("10/minute")
async def generate_report(request: Request):
...
Nginx — на уровне reverse proxy для грубой защиты до попадания в приложение:
limit_req_zone $http_x_api_key zone=api:10m rate=100r/m;
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
Usage Tracking: что и как считать
Метрики делятся на две группы. Billing metrics — то, за что платит клиент: количество API calls, объём переданных данных, количество активных пользователей, время вычислений. Operational metrics — для мониторинга: latency по percentiles, error rate, топ endpoints по нагрузке.
Архитектура сбора данных:
Синхронная запись в основную БД при каждом запросе — антипаттерн. Правильный подход:
- В middleware атомарно инкрементируем счётчик в Redis:
INCR usage:{tenant_id}:{date}:{endpoint} - Celery/BullMQ job каждые 5 минут сливает агрегаты из Redis в PostgreSQL
- Детальный лог запросов пишется асинхронно в ClickHouse или TimescaleDB для аналитики
-- Таблица агрегированного usage в PostgreSQL
CREATE TABLE api_usage_daily (
tenant_id UUID NOT NULL,
date DATE NOT NULL,
endpoint VARCHAR(200),
plan VARCHAR(50),
requests BIGINT DEFAULT 0,
bytes_in BIGINT DEFAULT 0,
bytes_out BIGINT DEFAULT 0,
errors_4xx INT DEFAULT 0,
errors_5xx INT DEFAULT 0,
PRIMARY KEY (tenant_id, date, endpoint)
);
Dashboard и алерты для клиентов
Клиент должен видеть своё потребление в реальном времени — это снижает количество неожиданных блокировок и support-тикетов. Минимальный набор: текущее использование vs квота (progress bar), график по дням за последние 30 дней, топ-5 endpoints по количеству вызовов.
Алерты: уведомление на email/webhook при достижении 80% квоты, предупреждение за сутки до конца расчётного периода с прогнозом превышения.
Интеграция с биллингом
При pay-per-use модели usage данные передаются в Stripe через Billing Meters API:
await stripe.billing.meters.createEvent({
event_name: 'api_requests',
payload: {
stripe_customer_id: tenant.stripeCustomerId,
value: requestCount,
},
timestamp: Math.floor(Date.now() / 1000),
});
Для фиксированных планов с overage — сравниваем usage с планом в конце биллингового периода и выставляем дополнительный invoice за превышение.
Типичные сроки
Базовый rate limiting с Redis и заголовками RFC 6585 — 2–3 дня. Usage Tracking с агрегацией в PostgreSQL и dashboard для клиента — 5–7 дней. Интеграция с Stripe Billing Meters и alerts — ещё 3 дня.







