Уявіть: ваш API падає під навантаженням, а в логах — тисячі запитів з однієї IP-адреси. На одному з наших проєктів партнерський сервіс зациклився і відправив 10 000 запитів на хвилину. Без rate limiting це призвело до падіння бази даних і простою на 4 години. Ми впровадили обмеження на рівні Nginx та застосунку — проблема зникла. Такі кейси — наша щоденна робота.
Ми реалізуємо rate limiting для API веб-застосунків на будь-якому стеку — від Laravel до NestJS, з Redis-бекендом, різними лімітами за тарифами та правильними заголовками відповіді. Нижче — перевірені підходи, які допоможуть уникнути простоїв та захистити інфраструктуру.
Як вибрати алгоритм rate limiting?
Вибір алгоритму залежить від характеру навантаження. Sliding Window усереднює запити по ковзному вікну, уникаючи вразливості Fixed Window: коли лічильник скидається кожні N секунд, клієнт може відправити 100 запитів наприкінці вікна і 100 на початку наступного — отримуємо 200 за секунду. Sliding Window цього не допускає. Token Bucket накопичує токени зі швидкістю refill rate, дозволяючи burst до bucket size — підходить для інтеграцій з нерегулярним трафіком. Leaky Bucket ставить запити в чергу з фіксованим drain rate, даючи максимально рівномірне навантаження, але можливі затримки.
| Алгоритм | Точність обліку | Burst-захист | Складність реалізації | Рекомендований сценарій |
|---|---|---|---|---|
| Fixed Window | Низька | Ні | Низька | Прості тарифи |
| Sliding Window | Висока | Так | Середня | API загального призначення |
| Token Bucket | Середня | Так | Середня | Burst-трафік партнерів |
| Leaky Bucket | Висока | Ні | Висока | Real-time системи |
Для більшості веб-застосунків оптимальний Sliding Window з Redis. Він дає рівномірне обмеження без сплесків на межах вікна. Якщо API обробляє burst-трафік (наприклад, від партнерських інтеграцій), обирайте Token Bucket. Для real-time систем зі стабільним потоком — Leaky Bucket.
Чому важливо комбінувати rate limiting на Nginx і в застосунку?
Nginx виступає першою лінією захисту: він може відсікти явні аномалії (наприклад, понад 1000 запитів на секунду з одного IP) на рівні сервера, не навантажуючи застосунок. Застосунок же забезпечує гнучкі ліміти за користувачами та тарифами. Такий дворівневий захист ефективніший за будь-яку одношарову реалізацію. Ви можете налаштувати Nginx з модулем limit_req для загального захисту, а в застосунку задати більш тонкі правила.
Реалізація в Laravel та NestJS
Laravel — використовуємо Throttle middleware та RateLimiter:
// app/Providers/RouteServiceProvider.php RateLimiter::for('api', function (Request $request) { $user = $request->user(); if (!$user) return Limit::perMinute(30)->by($request->ip()); return match($user->plan) { 'enterprise' => Limit::perMinute(1000)->by($user->id), 'pro' => Limit::perMinute(300)->by($user->id), default => Limit::perMinute(60)->by($user->id), }; }); Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () { Route::apiResource('articles', ArticleController::class); }); NestJS — модуль @nestjs/throttler з Redis-сховищем:
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis'; ThrottlerModule.forRoot({ throttlers: [ { name: 'short', ttl: 1000, limit: 10 }, { name: 'medium', ttl: 60000, limit: 300 }, { name: 'long', ttl: 3600000, limit: 5000 }, ], storage: new ThrottlerStorageRedisService(redisClient), }); | Аспект | Laravel | NestJS | Nginx |
|---|---|---|---|
| Гнучкість лімітів | Висока (за користувачами, тарифами) | Висока (за групами маршрутів) | Низька (тільки за IP) |
| Швидкість роботи | Середня (PHP) | Висока (Node.js) | Максимальна (C) |
| Централізоване зберігання | Redis | Redis | Немає (або зовнішній модуль) |
| Заголовки Retry-After | Автоматично | Автоматично | Вручну |
Дистриб'ютивний rate limiting з Lua-скриптом
Для декількох серверів застосунку — централізований Redis та Lua-скрипт для атомарного лічильника:
-- sliding_window.lua local key = KEYS[1] local now = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local limit = tonumber(ARGV[3]) redis.call('ZREMRANGEBYSCORE', key, 0, now - window) local count = redis.call('ZCARD', key) if count < limit then redis.call('ZADD', key, now, now .. math.random()) redis.call('EXPIRE', key, window / 1000) return 1 end return 0 Цей скрипт гарантує атомарність і підходить для розподіленої архітектури. Ми використовуємо його в проєктах з високим навантаженням.
Bypass-стратегії: що не обмежувати
Деякі запити мають обходити ліміти: внутрішні сервіси (IP-whitelist), webhook-ендпоінти, health check /health. Реалізуємо через умову в RateLimiter:
RateLimiter::for('api', function (Request $request) { if ($request->ip() === config('services.internal_ip')) { return Limit::none(); } // ... }); Типові помилки при впровадженні rate limiting
У 70% проєктів, які ми аудитували, використовували Fixed Window без урахування burst — це дозволяє клієнту обійти ліміт. Ще 20% ігнорують заголовки Retry-After, через що клієнти не знають, коли повторювати запит. Обмеження health check призводить до хибних спрацьовувань моніторингу. Ігнорування розподіленого зберігання — при декількох серверах лічильники розходяться.
Що входить у реалізацію під ключ
- Аналіз поточної архітектури та профілю навантаження.
- Вибір алгоритму (рекомендуємо Sliding Window + Redis).
- Налаштування лімітів за ендпоінтами та тарифами.
- Інтеграція з Nginx як перша лінія захисту.
- Додавання заголовків
X-RateLimit-*таRetry-After. - Моніторинг 429-відповідей (Grafana + алерти).
- Документація та навчання команди.
Строки
Базова реалізація з Redis та Sliding Window — від 1 до 2 днів. Розширена з Nginx, Lua-скриптами та моніторингом — від 3 до 4 днів. Вартість розраховується індивідуально після аналізу проєкту. Оцінимо ваш проєкт за 1 день — зв'яжіться з нами для консультації. Замовте впровадження rate limiting, і ваш API витримає будь-яке навантаження.
Ми реалізували rate limiting для 20+ проєктів різної складності. Гарантуємо, що після нашої реалізації ви забудете про проблеми з перевантаженнями.







