Что делать при ошибках и лимитах запросов
Как обработать ошибки поиска в коде, что означают коды ошибок, как обойти ограничение запросов.
Ошибки и ограничения запросов
Когда что-то не так с вашим запросом (неверный ключ, некорректный синтаксис, превышен лимит запросов), AACsearch вернёт ошибку с кодом.
Как выглядит ошибка
{
"error": "rate_limit_exceeded",
"message": "Превышено 60 запросов в минуту, повторите через 15 секунд",
"retryable": true,
"requestId": "req_01HX9JKP7QZK7M5T2N4F8R6Y3W",
"docsUrl": "https://docs.aacsearch.com/troubleshooting/rate-limits"
}| Поле | Стабильно? | Используйте для |
|---|---|---|
error | да — машиночитаемый, стабильный между версиями | switch/case в обработчике ошибок |
message | нет — для людей, может меняться | вывод в логи / dashboards для инженеров |
retryable | да — boolean | подключите к retry-логике (не парсите код вручную) |
requestId | да — opaque ULID | передайте в support чтобы найти запрос в логах |
docsUrl | да — присутствует для документированных ошибок | ссылка из вашего UI ошибок |
requestId также возвращается в заголовке ответа X-Request-Id — на случай если тело ответа не парсится (например 504 от upstream-прокси).
Типичные ошибки и решения
❌ Ошибки в вашем коде (4xx) — не повторяйте
| Ошибка | Проблема | Как исправить |
|---|---|---|
unauthorized | Неверный или истёкший API-ключ | Проверьте ключ в панели управления |
forbidden | Ключ не имеет права на этот индекс | Используйте правильный ключ или добавьте область |
invalid_filter | Неверный синтаксис в filterBy | Проверьте синтаксис фильтров (см. раздел выше) |
invalid_sort | Неверный синтаксис в sortBy | Используйте правильное поле и направление |
invalid_request | Отсутствует обязательное поле | Добавьте indexSlug и q |
index_not_found | Индекс с этим названием не существует | Проверьте название индекса (indexSlug) |
Действие: исправьте запрос, не повторяйте слепо.
⏳ Превышены лимиты (429) — подождите и повторите
| Ошибка | Проблема | Что делать |
|---|---|---|
rate_limit_exceeded | Слишком много запросов за минуту (> 60) | Подождите (секунды в заголовке Retry-After), потом повторите |
quota_exceeded | Исчерпана месячная квота поисковых единиц | Обновите план в панели (Settings → Billing) или подождите до 1-го числа месяца |
Действие: подождите необходимое время, повторите запрос.
🔥 Ошибки сервера (5xx) — повторите с задержкой
| Ошибка | Проблема | Что делать |
|---|---|---|
search_failed | Сервер AACsearch недоступен | Повторите через 1 сек.; если ещё раз не удалось → ошибка |
service_unavailable | API AACsearch на техническом обслуживании | Повторите с экспоненциальной задержкой (1s, 2s, 4s) |
Действие: повторите с задержкой, не более 3 попыток.
Ограничение частоты запросов
Лимит: 60 запросов в минуту на один API-ключ.
Если вы отправляете запросы слишком часто, получите ошибку:
{
"error": "rate_limit_exceeded",
"message": "Превышено 60 запросов в минуту. Повторите через 15 секунд."
}Как узнать статус лимита
В каждом ответе AACsearch добавляет заголовки:
X-RateLimit-Limit: 60 // Максимум запросов за минуту
X-RateLimit-Remaining: 42 // Запросов осталось в текущей минуте
X-RateLimit-Reset: 1717201234 // Когда сбросится (Unix время)
Retry-After: 15 // Если лимит превышен: подождите столько секундИспользуйте X-RateLimit-Remaining чтобы предугадать, когда приближается лимит.
Как избежать превышения лимита
- Кэш результатов: не делайте одинаковые поиски дважды
- Группируйте запросы: используйте мультипоиск для нескольких поисков сразу
- Дебаунс автодополнения: не ищите на каждый символ, подождите паузу
- Избегайте параллельных запросов: отправляйте поиски один за другим, не все сразу
Пример: обработка лимита в коде
try {
const results = await client.search({ q: "shoes" });
} catch (err) {
if (err.code === "rate_limit_exceeded") {
console.log("Лимит исчерпан, подожду 15 секунд...");
setTimeout(() => {
// Повторить запрос
}, 15000);
}
}Месячная квота
Каждый план имеет месячный лимит на поиски (в "единицах поиска").
Когда исчерпается квота:
- Ошибка:
quota_exceeded - Решение: обновите план (Settings → Billing) или подождите до 1-го числа следующего месяца
- Квота сбрасывается автоматически 1-го числа каждого календарного месяца
Как узнать, сколько квоты осталось
В панели управления (Search → Overview) видна полоска использованной квоты.
Обработка ошибок в коде
Используйте готовые библиотеки для автоматических повторов:
// JavaScript с библиотекой p-retry
import pRetry from "p-retry";
const results = await pRetry(
() => client.search({ q: "headphones" }),
{
retries: 3, // Максимум 3 попытки
factor: 2, // Экспоненциальная задержка: 1s, 2s, 4s
minTimeout: 1000, // Минимум 1 секунда между попытками
}
);# Python с retry
from retry import retry
@retry(tries=3, delay=1, backoff=2)
def search():
return client.search(q="headphones")
results = search()Поведение модулей CMS при повторах
Буфер приёма данных AACsearch рассчитан на повторные попытки:
- Запросы синхронизации коннектора (
sync/full,sync/delta) ставят документы в очередьSearchIngestBuffer - Фоновый воркер повторяет неудавшиеся строки с экспоненциальной задержкой
- Модулю CMS не нужно реализовывать повтор для отдельных документов — буфер берёт это на себя
- Модуль CMS должен повторять сам HTTP-запрос при ответах 5xx (но не на 4xx)
Отладка при разработке
При ошибках 502 во время разработки:
- Проверьте, работает ли AACSearch:
curl http://localhost:8108/health - Проверьте логи API на наличие исходной ошибки AACSearch (она сопоставляется перед отправкой ответа клиенту)
- Проверьте
AACSEARCH_HOST,AACSEARCH_PORT,AACSEARCH_API_KEYв.env.local
При ошибках 401:
- Убедитесь, что срок действия ключа не истёк
- Убедитесь, что префикс ключа соответствует операции (
ss_search_*для поиска,ss_connector_*для приёма данных) - Для ограниченных токенов: проверьте поле
expiresAtв декодированной нагрузке
Request ID и trace ID
Каждый ответ содержит два корреляционных идентификатора — передавайте их в поддержку, и мы сможем найти запрос в логах без переписки.
| Заголовок | Поле тела | Откуда |
|---|---|---|
X-Request-Id | requestId | Генерируется на edge API на каждый запрос. Присутствует всегда. |
X-Trace-Id | traceId (только если включён distributed tracing) | OpenTelemetry trace ID, прокидывается через worker → AACSearch → ответ |
Захватите их клиентом из SDK:
import { SearchClient } from "@aacsearch/client";
try {
const result = await client.search({ q: "shoes" });
} catch (err) {
if (err instanceof AacSearchError) {
console.error("aacsearch error", {
code: err.code,
requestId: err.requestId, // для тикетов в поддержку
traceId: err.traceId, // для дашбордов distributed tracing
});
}
}Серверные обработчики должны логировать их при каждом запросе — успешном и провальном:
const result = await client.search(opts);
logger.info({
event: "search.ok",
requestId: result.requestId,
searchTimeMs: result.searchTimeMs,
});Так когда клиент сообщит "поиск тормозил в 14:32" — вы найдёте requestId в логах и совпадение AACsearch-запроса. С нашей стороны мы сможем join по этому ID.
SDK error class
TypeScript SDK выставляет типизированный AacSearchError, оборачивающий каждый ответ не 2xx:
import { AacSearchError } from "@aacsearch/client";
try {
await client.search({ q: "shoes" });
} catch (err) {
if (err instanceof AacSearchError) {
err.code; // string — код ошибки из тела, стабильный
err.message; // string — для людей
err.status; // number — HTTP status (0 для сетевых ошибок)
err.retryable; // boolean — из тела, fallback к status >= 500
err.requestId; // string | undefined
err.traceId; // string | undefined
err.docsUrl; // string | undefined
err.response; // Response | undefined — сырой fetch Response если доступен
}
}Та же форма в Python (SdkError) и PHP (AacSearchException).
Рецепты UI-состояний
Конкретные React-компоненты для четырёх самых частых состояний ошибок. Все headless — встройте в свою дизайн-систему.
Невалидный ключ
function SearchInvalidKey({ requestId }: { requestId?: string }) {
return (
<div role="alert" className="search-error">
<h3>Поиск настроен некорректно</h3>
<p>Ваш API-ключ невалиден, истёк или был отозван.</p>
<p>
Если вы владелец сайта — выпустите новый ключ в панели AACsearch. Иначе попробуйте позже.
</p>
{requestId && <small>Reference: {requestId}</small>}
</div>
);
}Quota exceeded
function SearchQuotaExceeded({ resetDate }: { resetDate: Date }) {
return (
<div role="alert" className="search-error">
<h3>Поиск временно недоступен</h3>
<p>Сайт исчерпал месячный лимит поиска.</p>
<p>Сервис восстановится {resetDate.toLocaleDateString()}.</p>
<p>
<a href="https://app.aacsearch.com/billing">Сменить тариф</a>
</p>
</div>
);
}Нет результатов
function NoResults({ query }: { query: string }) {
return (
<div className="no-results">
<h3>Ничего не найдено по "{query}"</h3>
<p>Попробуйте более короткий запрос, другие ключевые слова или просмотрите наши категории ниже.</p>
<a href="/c/all">Все товары →</a>
</div>
);
}Для богатого UI пустых результатов с did-you-mean, популярными категориями и fallback-бестселлерами — см. рецепт no-results.
Ошибка сервера / сети
function SearchUnavailable({
requestId,
onRetry,
}: {
requestId?: string;
onRetry: () => void;
}) {
return (
<div role="alert" className="search-error">
<h3>Поиск временно недоступен</h3>
<p>Не удалось достучаться до сервиса поиска. Попробуйте через минуту.</p>
<button onClick={onRetry}>Повторить</button>
{requestId && <small>Reference: {requestId}</small>}
</div>
);
}Связывание
Используйте код ошибки SDK для диспетчеризации:
function SearchResults({ query }: { query: string }) {
const { data, error, refetch } = useQuery({
queryKey: ["search", query],
queryFn: () => client.search({ q: query }),
});
if (error instanceof AacSearchError) {
switch (error.code) {
case "unauthorized":
case "invalid_or_revoked_key":
return <SearchInvalidKey requestId={error.requestId} />;
case "quota_exceeded":
return <SearchQuotaExceeded resetDate={nextMonthFirst()} />;
case "rate_limit":
case "search_failed":
case "service_unavailable":
return <SearchUnavailable requestId={error.requestId} onRetry={refetch} />;
default:
return <SearchUnavailable requestId={error.requestId} onRetry={refetch} />;
}
}
if (data?.found === 0) return <NoResults query={query} />;
return <ResultGrid hits={data?.hits ?? []} />;
}Полный диагностический поток на каждый код ошибки — см. хаб диагностики.
Связанные
- Хаб диагностики — деревья симптом → решение
- Ошибки авторизации — глубокое погружение в 401/403
- Лимиты и квоты — глубокое погружение в 429
- Server-side helpers — production retry-паттерны
- No-results recommendations — богатый UX пустых состояний