Diil Docs
  1. Документация
  2. Руководства

Лимиты запросов и как в них не упереться

Обновлено:

Content API щедрый, но не бездонный. Несколько простых лимитов держат его быстрым для всех, а сайт, который разумно кэширует, с ними просто не встретится. Считайте эту страницу знаками ограничения скорости на дороге, по которой вы почти всегда едете гораздо медленнее.

Сами лимиты

Что считаетсяЛимитЧто будет при превышении
Запросы с одного IP-адреса300 в минутуЭтот IP получает 429 до конца минуты.
Запросы с одним ключом сайта (со всех IP вместе)600 в минутуЗапросы с этим ключом получают 429 до конца минуты.
Запросы с неверным ключом с одного IP20 в минутуIP блокируется до конца минуты — даже запросы с правильным ключом получают 429.

Фиксированные окна по минуте

Счётчики работают фиксированными окнами по одной минуте, а не скользящим средним. Запросы набивают счётчик, минута заканчивается — он начинается с нуля. Отсюда два практических вывода:

  • Всплеск в самом начале минуты — не беда, если за всю минуту вы уложились в лимит.
  • Если уже пришёл 429, в эту минуту ничего не поможет. Поможет следующая.

Какой лимит вы встретите первым

Зависит от того, откуда идут запросы:

  • Серверный рендеринг на одной машине. Все запросы уходят с IP вашего сервера, так что первым потолком станут 300 в минуту на IP. У нескольких серверов у каждого свои 300, но вместе они всё равно делят 600 на ключ.
  • Запросы из браузера. У каждого посетителя свой IP, поэтому лимит на IP почти никогда не мешает. Зато все посетители ходят с одним ключом сайта, а у ключа 600 запросов в минуту на весь сайт. Кэш браузера помогает каждому посетителю по отдельности, но не толпе.

Как выглядит ответ 429

429 Too Many Requestshttp
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Cache-Control: no-store

{"message":"rate_limit_exceeded"}

Вот и всё. Заголовка Retry-After нет, заголовков RateLimit-* тоже — не ищите. Раз окна — это ровные минуты, правило простое: подождите около минуты (с небольшой случайной добавкой, о ней ниже) и повторите запрос.

Повтор с паузой и случайным разбросом

Небольшая обёртка над fetch, которая на каждую беду реагирует по-своему:

  • 429 — пересидеть окно: около 60 секунд плюс несколько случайных.
  • 5xx и сетевые ошибки — экспоненциальная пауза со случайным разбросом: примерно 0,5–1 с, потом 1–2 с, потом 2–4 с.
  • Любые другие 4xx — никаких повторов. Опечатку в маркере ожиданием не исправить.
lib/fetch-with-retry.tsts
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

type RetryOptions = {
  retries?: number;   // сколько дополнительных попыток
  maxWaitMs?: number; // дольше этого не ждём — сразу отдаём ошибку
};

export async function fetchWithRetry(
  url: string,
  init: RequestInit = {},
  { retries = 3, maxWaitMs = 70_000 }: RetryOptions = {},
): Promise<Response> {
  for (let attempt = 0; ; attempt++) {
    let res: Response | undefined;
    let error: unknown;

    try {
      res = await fetch(url, init);
      // 2xx, 304 или 4xx, который ожиданием не лечится (400, 401, 404…): отдаём как есть
      if (res.status !== 429 && res.status < 500) return res;
    } catch (err) {
      error = err; // беда с сетью: DNS, таймаут, оборванное соединение
    }

    const wait =
      res?.status === 429
        ? 60_000 + Math.random() * 5_000             // окно — минута: пересиживаем её плюс разброс
        : 2 ** attempt * 500 * (1 + Math.random());  // 5xx / сеть: 0,5–1 с, 1–2 с, 2–4 с…

    if (attempt >= retries || wait > maxWaitMs) {
      if (res) return res; // пусть вызывающий код увидит 429 / 5xx
      throw error;
    }
    await sleep(wait);
  }
}

Там, где никто не ждёт, вызывайте её терпеливо, а там, где ждёт человек, — нетерпеливо:

Два способа вызоваts
const headers = { 'x-crm-key': process.env.CRM_KEY! };
const url = 'https://back.sitecog.com/content/v1/pages/home?lang=en';

// Скрипт сборки или фоновая задача: спокойно пересидит 429
const res = await fetchWithRetry(url, { headers });

// Рендер страницы для посетителя: минуту страницу никто ждать не будет.
// 429 вернётся сразу, а вы покажете копию, сохранённую раньше.
const quick = await fetchWithRetry(url, { headers }, { retries: 1, maxWaitMs: 2_000 });

Арифметика SSR: сколько запросов делает ваш сервер

Давайте посчитаем. Допустим, при рендере страница запрашивает две вещи: саму себя (/v1/pages/about) и общие шапку с подвалом (/v1/pages/common). Это два запроса на один просмотр. На сайте 30 страниц на 2 языках.

ПодходЗапросов к API в минутуГде ломается
Без кэша, страница + common на каждый просмотр2 × просмотры150 просмотров в минуту — это 2,5 посетителя в секунду, хватит одной рассылки
Без кэша, каждый блок отдельным запросом (скажем, 20 блоков)20 × просмотры15 просмотров в минуту — оживлённый обеденный перерыв
revalidate: 60, страница + commonне больше 30 × 2 + 1 × 2 = 62нигде — те же 62 и при 10 просмотрах, и при 100 000

Последняя строка — и есть вся суть: с кэшем на сервере каждый уникальный адрес запрашивается не чаще раза в минуту, и нагрузка на API ограничена размером сайта, а не его популярностью. Попасть в тренды перестаёт быть проблемой инфраструктуры.

Следите за сборкой

Статическая генерация — всплеск по определению: сборка, которая параллельно рендерит 400 страниц на 2 языках, за несколько секунд может выпустить с одной машины 800 запросов — намного больше 300 в минуту. Ограничьте параллельность несколькими страницами за раз и используйте терпеливый fetchWithRetry из примера выше: тогда 429 поставит сборку на паузу на минуту, а не уронит её.

Как держаться от лимитов подальше

  1. Кэшируйте у себя. revalidate в Next.js, ISR или маленький кэш в памяти на любом Node-сервере. Готовые рецепты — на странице Кэш и ETag.
  2. Один запрос страницы вместо россыпи блоков. /v1/pages/:marker отдаёт все секции и блоки разом. Заказывать пиццу по кусочку весело только курьеру.
  3. Общий контент — одним запросом. Шапка и подвал на странице commonодинаковы для всех маршрутов — пусть их обслуживает один закэшированный запрос.
  4. Пользуйтесь ETag. Запросов меньше не станет, но каждый повторный почти ничего не будет стоить по трафику. Подробнее — в разделе ETag и 304.
  5. Не опрашивайте API по кругу. Спрашивать каждую секунду «а не поменялось ли что?» — ровно тот трафик, ради которого лимиты и придуманы. Кэша на 60 секунд редакторам хватает, а режим Live показывает правки мгновенно.
  6. Следите за ключами. Старая тестовая выкладка или забытая cron-задача с отозванным ключом на том же сервере, что и боевой сайт, может за минуту исчерпать 20 запросов с неверным ключом — и тогда 429 получит и боевой сайт с этого IP. Одна паршивая овца, как говорится.
Быстрая проверка ключаbash
curl -s -o /dev/null -w "%{http_code}\n" \
  "https://back.sitecog.com/content/v1/langs" -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"
# 200 — всё хорошо, 401 — почините ключ, пока его никто не начал повторять

Всё-таки поймали 429 и хотите знать, что ещё может пойти не так? Все статусы — на странице Ошибки.