Реалізація 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)
);
Панель та алерти для клієнтів
Клієнт повинен бачити своє споживання в реальному часі — це знижує кількість неочікуваних блокувань та 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 та панеллю для клієнта — 5–7 днів. Інтеграція з Stripe Billing Meters та алерти — ще 3 дні.







