Лимиты и квоты
Как AACsearch ограничивает запросы на ключ, что значит 429 и как восстановиться.
Лимиты и квоты
AACsearch ограничивает две сущности независимо:
- Rate limit — запросов в минуту, на API-ключ. Бакет на ключ, сбрасывается каждую минуту.
- Квоты — суммарные search-units в месяц, на организацию. Потолок биллинг-тарифа.
429 на публичном API — всегда rate limit. Превышение квоты — другой код, см. Ответы по квоте.
Модель rate limit
У каждого постоянного ключа поле rateLimitPerMinute. Дефолт — 600 (10 rps постоянно). На каждый запрос auth-layer инкрементирует счётчик ключа в PostgreSQL; превысил — отказ:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
{
"error": "rate_limited",
"limit": 600
}Счётчик сбрасывается на старте каждой минуты по часам. Leaky bucket-сглаживания нет — выстрелили 600 в первую секунду, остаток минуты отказывает; в начале следующей — снова свежие 600.
Да, первая секунда минуты съест внезапный burst. И ещё: стабильные 10 rps лимит не задевают, а burst в 600 запросов, попавший на границу двух минут, частично отказывает. Размазывайте retry.
Задание лимита
В дашборде: API keys → Edit → Rate limit per minute. Ориентиры:
| Роль ключа | Лимит / минута |
|---|---|
| Браузерный поиск (виджет) | 600 – 1 500 |
| Серверный ingest-воркер | 300 – 600 |
| Admin / дашборд (низкий объём) | 60 |
| Connector key (CMS плагин) | 1 000 |
| Синтетический probe | 30 |
Это старт. Понаблюдайте за Usage неделю — если 99-я перцентиль ниже половины лимита, можно опустить. Постоянно 80 %+ — либо повышайте, либо делите трафик на два ключа.
Восстановление после 429
Если клиент получил 429:
- Не ретраите сразу. Retry в той же минуте снова упадёт.
- Ждите следующую минуту. Можно по
Retry-After, если есть. - Джиттер. Все клиенты, ретраящие ровно в начале следующей минуты, создают синхронный stampede.
Рабочий backoff:
async function searchWithRetry(payload, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch("/api/v1/.../search", { method: "POST", body: payload });
if (res.status !== 429) return res;
const ms = 1000 * 2 ** attempt + Math.random() * 1000;
await new Promise((r) => setTimeout(r, Math.min(ms, 60_000)));
}
throw new Error("Rate limited after retries");
}Браузерные клиенты должны деградировать мягко: «Searching…» с задержкой, а не спам retry, пока пользователь печатает.
Почему вы внезапно «выше лимита»
| Причина | Диагноз |
|---|---|
| Один ключ в нескольких воркерах. | Все вкладываются в один бакет. Дашборд показывает скачки после деплоя. |
| Search-as-you-type без debounce. | На каждое нажатие — запрос. Debounce 150–250 мс. |
| Webhook fan-out → реиндекс. | Бэклог webhook-доставок может разгонять записи. |
| Бот. | API keys → Usage → User-Agent breakdown. Если трафик из одного UA, которого вы не знаете — скрапер. CSP плюс ротация ключа. |
Квоты на организацию
Помимо rate limit, у организации месячная квота в search-units. Search-unit — один публичный запрос (suggest, multi-search, federated, geo и т. п.).
Квота проверяется после rate-limit-gate, но до запуска поиска. Когда организация ушла за месячную квоту:
- Поиск продолжает работать, пока не выйдет за план + overage-бюджет.
- Дашборд показывает предупреждение на 80 % и баннер «квота закончилась» при исчерпании.
- Если есть баланс wallet и включён overage-bypass — списываем с wallet и продолжаем.
См. Billing wallet — как работает overage (у Enterprise может быть flat-rate без overage).
Ответы по квоте
Когда и wallet/overage путь иссяк:
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"error": "quota_exhausted",
"resetAt": "2026-06-01T00:00:00Z",
"topUpUrl": "https://app.aacsearch.com/organization/billing/wallet"
}402 — сознательно. 429 — «идёте слишком быстро», 402 — «использовали то, за что заплатили». В клиенте обрабатывайте по-разному.
Заголовки
| Заголовок | Значение |
|---|---|
x-ratelimit-limit | rateLimitPerMinute ключа. |
x-ratelimit-remaining | Осталось в текущей минуте. |
x-ratelimit-reset | UTC времени сброса бакета. |
retry-after | Секунды до повтора (на 429). |
x-request-id тоже возвращается на каждый запрос — кладите в логи, самая полезная вещь для эскалации.
Что не ест rate limit
GET /api/v1/health— health probe.GET /scim/v2/ServiceProviderConfig— SCIM discovery.- Webhook-и от AACsearch к вам — частота доставки задаётся нами, не вашим лимитом.
Частые ошибки
- «Поставлю очень высокий лимит, чтобы 429 не было». Лимит защищает вас от стампида, который стоит денег. Ставьте туда, где трафик должен быть.
- Один ключ на команды. Две команды на одном ключе не могут диагностировать чужой пик. По ключу на сервис.
- Backoff на неправильный ответ.
5xx— да, backoff.4xx(включая 429) — клиент чинит. Из 4xx ретраить можно только 429 и 408.
См. также
- Мониторинг — заранее заметить
- Статус и инциденты — когда виноват кластер
- Best practices — гигиена ключей