Главная / Статьи / Почему повышение конкурентности приводит к ошибкам HTTP 429: скорость запуска и лимиты по хосту

Почему повышение конкурентности приводит к ошибкам HTTP 429: скорость запуска и лимиты по хосту

Увеличение количества работников без регулирования скорости запуска и лимитов на одного хоста приводит к всплескам нагрузки, вызывающим ошибки 429. Пропускная способность достигается за счёт контролируемой одновременной работы, механизмов откладывания запросов и разумного проектирования очередей.

3571 слов

Кратко: Повышение уровня конкурентности преподносится как бесплатный параллелизм: больше процессоров, больше запросов, больше данных. Десять кажутся хорошим показателем, поэтому двадцать должны быть лучше, а пятьдесят — неотразимы. Однако увеличение количества ресурсов влияет не только на конкурентность. Оно также определяет, с какой интенсивностью клиент обращается к хосту с самого начала, а при работе с несколькими хостами — каков уровень конкурентности на каждом хосте, даже если никто этого не настраивал. Когда эти скрытые механизмы выходят из строя, код состояния часто становится HTTP 429, что означает «слишком много одновременных запросов». Команды тогда сокращают количество ресурсов или добавляют прокси, а затем продолжают работу. Не выявив истинную причину, они теряют возможность получить более высокую пропускную способность.

Часть I — Конкурентность против скорости запуска: одна настройка пула контролирует два ограничения

Размер пула рабочих процессов (p-limit(n), семафор, количество потоков) определяет максимальное количество операций, которые могут выполняться одновременно. Он не ограничивает скорость начала новой работы. В момент t=0 пул с пятьюдесятью ожидающими задачами может начать обработку пятидесяти запросов за один такт — происходит резкий скачок скорости запуска, — хотя в устойчивом режиме одновременная обработка будет выглядеть как «только пятьдесят». Ограничители скорости учитывают как этот всплеск при запуске, так и постоянный уровень параллелизма.

Измерение всплеска скорости запуска

Помогает инструмент для воспроизводимых тестов. Возьмем небольшой набор URL-адресов статей из Wiki Arch Linux:

https://wiki.archlinux.org/title/Arch_Linux
https://wiki.archlinux.org/title/Installation_guide
https://wiki.archlinux.org/title/Pacman
https://wiki.archlinux.org/title/Systemd
...and so on

Создадим нагрузку из 100 запросов, циклируя по этому списку:

function buildWorkload(urls, n) {
  return Array.from({ length: n }, (_, i) => urls[i % urls.length]);
}
async function runPooledFetch(urls, concurrencyLimit, agent, options = {}) {
  const limit = pLimit(concurrencyLimit);
  const results = await Promise.all(
    urls.map((url) => limit(() => fetchOne(url, agent)))
  );
  // ...
}

Запустите тест с разными размерами пулов, отнесите каждый результат к категории «ок» или 429 (и другим ошибкам), а также записывайте количество завершённых запросов в секунду наряду с количеством результатов типа «ок». Это различие имеет значение: даже быстрый ответ 429 увеличивает показатель «завершённых запросов в секунду», хотя на самом деле страница не предоставляется.

Полезная пропускная способность снижается по мере роста глобальной одновременности

При фиксированной нагрузке в 100 запросов, при которой меняется только размер пула, прямое подключение демонстрирует плохую производительность. При одновременном обработке 1 запроса практически все 100 запросов успешно обрабатываются, и скорость завершения запросов составляет около 2,4/с — при этом приходится ждать полной обработки без возможности параллельного запуска. При одновременном обработке 10 запросов количество успешно обработанных страниц снижается до примерно 42, а 58% запросов возвращают ошибку 429; при этом скорость завершения запросов возрастает до примерно 23,5/с. При одновременном обработке 25 и 50 запросов количество успешных операций продолжает снижаться (примерно 24/100, затем 16/100), в то время как скорость завершения запросов остается на уровне высоких двузначных значений (~18,2/с и 21,8/с).

Пик нагрузки при уровне одновременных запросов 10 достигается при 23,5 завершённых запросов в секунду — это максимальное значение в таблице, но при этом одно из самых низких значений количества успешных запросов. Оптимизация с учётом количества завершённых запросов в секунду предполагает использование настройки, которая направляет большую часть ресурсов на блокировку запросов. Полезной является величина успешных запросов в секунду, а не просто общее количество завершённых запросов.

Установка лимита скорости начала обработки запросов при том же уровне одновременных запросов решает проблему

Сохраняйте размер пула без изменений и устанавливайте ограничения на то, как скоро может быть начата обработка следующего запроса. Минимальный интервал определяется на основе максимального количества запросов в секунду:

// minGapMs derived from --max-rps; pool concurrency is untouched
if (minGapMs > 0) {
  const now = Date.now();
  const waitMs = Math.max(0, nextStartAt - now);
  if (waitMs > 0) await sleep(waitMs);
  nextStartAt = Math.max(Date.now(), nextStartAt) + minGapMs;
}

Благодаря контролю темпов обработки тот же уровень одновременных запросов, который ранее приводил к перегрузке хоста, теперь позволяет обработать почти все страницы, поскольку всплеск начала обработки исчезает. Правильная модель понимания заключается в использовании двух параметров: уровень одновременных запросов и скорость начала обработки. Сведение их к одному числу скрывает причины сбоев.

Почему пул HTTP-клиентов отправляет всплеск запросов в момент t=0

Пулы не учитывают принципы умеренного темпа работы. Они знают только о свободных слотах. При запуске все слоты свободны, поэтому все задачи в очереди, которые могут быть выполнены, действительно выполняются. Повторное использование соединений и мультиплексация HTTP/2 могут сделать этот всплеск ещё интенсивнее. Только контроллер доступа — бак с токенами, правило минимального интервала, «утекающий» бак — может регулировать начало работы независимо от ограничений, действующих в процессе выполнения запросов.

Решают ли домашние прокси проблему всплеска скорости запуска?

Принцип умеренного темпа работы помогает обеспечить корректность: собирать данные без резкого увеличения нагрузки на целевой сервер. Однако инженерные реалии иногда требуют такой скорости, которую невозможно достичь с ограничением 2,4 запроса в секунду. Тот же наивный подход — отсутствие ограничения скорости запуска, всплеск запросов в момент t=0 — при использовании домашних прокси позволяет добиться примерно 98–99/100 успешных операций практически без блокировок на различных уровнях одновременной работы, в то время как прямой путь по-прежнему сталкивается с серьёзными проблемами.

Это не означает, что прокси устраняют необходимость понимания скорости начала запросов. Они влияют на репутацию IP-адреса и на то, насколько агрессивно сайт распределяет трафик. Они могут скрыть всплеск нагрузки, который мог бы привести к заблокировке отдельного IP-адреса. Если требование к продукту — собирать данные без создания впечатления массового наплыва, то контроль темпа запросов остается основным инструментом; прокси служат лишь дополнительным слоем для управления пропускной способностью и репутацией, а не заменой методов измерения количества запросов.

При создании прокси-агента обычно берутся учетные данные из окружения:

function buildProxyAgent() {
  const user = process.env.BRIGHT_DATA_PROXY_RESIDENTIAL_USERNAME;
  const pass = process.env.BRIGHT_DATA_PROXY_RESIDENTIAL_PASSWORD;
  if (!user || !pass) return null;
  const host = process.env.BRIGHT_DATA_PROXY_HOST || 'brd.superproxy.io';
  const port = process.env.BRIGHT_DATA_PROXY_PORT || '33335';
  const proxyUrl = `http://${encodeURIComponent(user)}:${encodeURIComponent(pass)}@${host}:${port}`;
  return new HttpsProxyAgent(proxyUrl);
}
const residentialAgent = buildProxyAgent();
await runPooledFetch(tasks, 50, residentialAgent);

Используйте их осознанно, фиксируйте, был ли запрос направлен напрямую или через прокси, а также записывайте наблюдаемую скорость начала запросов, чтобы понимать, какой фактор повлиял на результат.

Часть II — Одновременность запросов к отдельным хостам

Глобальные пулы также скрывают еще одно возникающее ограничение. Рассмотрим следующее:

const limit = pLimit(50);
await Promise.all(urls.map((url) => limit(() => fetchOne(url))));

Наличие пятидесяти глобальных слотов, распределенных между несколькими хостами, не означает, что каждый хост будет обрабатывать не более пятидесяти запросов одновременно. Порядок очереди и различия в времени отклика определяют, сколько запросов фактически принимает тот или иной хост. Смешанный список может казаться сбалансированным на бумаге:

1. arch
2. github
3. arch
4. mdn
5. npm
6. arch
7. cloudflare

однако он все равно может приводить к всплескам нагрузки на строгий хост, если его URL-адреса сосредоточены в наборе для обработки.

Тестирование одновременной работы на каждом хосте

Используйте ту же среду тестирования с двумя хостами:

  • Строгий хост — Arch Linux Wiki (10 URL-адресов статей), который блокируется при высокой нагрузке, как и в части I.
  • Более гибкий хост — страницы каталога books.toscrape.com (10 URL-адресов), которые редко блокируются и служат контрольной группой. Если среда тестирования не работает, проблема в клиенте.

Периодический список даёт 20 URL × 5 повторений = 100 запросов при глобальной одновременности 50. Списки фикстур и инструменты создания находятся в репозитории open concurrency-trap-bench (например, urls-mixed-arch-books.txt).

https://wiki.archlinux.org/title/Arch_Linux
https://books.toscrape.com/catalogue/page-1.html
https://wiki.archlinux.org/title/Installation_guide
https://books.toscrape.com/catalogue/page-2.html
https://wiki.archlinux.org/title/Pacman
https://books.toscrape.com/catalogue/page-3.html
...and so on

Упорядоченные нагрузки могут работать по принципу круговой очереди, блокироваться по хостам или перемешиваться с использованием заданного параметра:

function buildOrderedWorkload(urls, pattern, { repeatsPerUrl = 5, seed = null } = {}) {
  const n = urls.length * repeatsPerUrl;
  let list = Array.from({ length: n }, (_, i) => urls[i % urls.length]);
if (pattern === 'block') {
    // AxN, BxN, and so on. Repeat each URL before advancing.
    const out = [];
    for (const url of urls) {
      for (let i = 0; i < repeatsPerUrl; i++) out.push(url);
    }
    return out;
  }
if (pattern === 'shuffle') {
    // Fisher–Yates with a fixed seed so the run is reproducible
    for (let i = list.length - 1; i > 0; i--) {
      seed = (Math.imul(1664525, seed) + 1013904223) >>> 0;
      const j = seed % (i + 1);
      [list[i], list[j]] = [list[j], list[i]];
    }
  }
  // 'round-robin' leaves the alternating list as-is
return list;
}

Измеряйте пик нагрузки на каждом хосте на основе событий начала/завершения:

function maxConcurrentPerHost(results) {
  const eventsByHost = new Map();
  for (const r of results) {
    const host = new URL(r.url).hostname;
    if (!eventsByHost.has(host)) eventsByHost.set(host, []);
    const end = r.startedAt + r.ms;
    eventsByHost.get(host).push({ t: r.startedAt, delta: 1 }, { t: end, delta: -1 });
  }
  // sort events by time, sweep: +1 on start, -1 on finish, track max
}

Глобальная одновременность остаётся неизменной; меняется только порядок обработки. Это позволяет отделить нагрузку на каждый хост от размера пула.

Как порядок очереди влияет на одновременность обработки по хостам

Реальные скрейперы никогда не работают по идеальной круговой очереди постоянно. Сайт-карты, диаграммы зависимостей и очереди повторных попыток перестраивают порядок обработки задач. Поэтому одинаковые глобальные настройки могут приводить к разным пикам нагрузки на отдельных хостах.

1. Круговая очередь: равномерное чередование хостов

Периодический список (A B A B …) распределяет нагрузку. Пик объема работы на строгом хосте остается относительно умеренным, поскольку другой хост постоянно занимает свободные слоты.

2. Блокировка: группировка запросов к одному хосту

Группировка всех URL Arch, а затем всех URL книг обеспечивает строгому хосту длительный период активной работы. Пик объема работы на этом хосте растет в зависимости от общего объема данных, несмотря на то, что «уровень конкурентности по-прежнему составляет 50».

3. Шифрование (зерно 42): случайный порядок

Шифрование с заданным зерном находится между крайними вариантами и имитирует случайный порядок выполнения запросов в реальных условиях. Пики изменяются в зависимости от зерна; главный урок заключается в чувствительности к параметрам, а не в существовании какого-то магического порядка.

Одинаковый уровень глобальной конкурентности, разная нагрузка на каждого хоста

При таких условиях показатель успешности на строгом хосте достигает своего максимума в процессе обработки запросов более точно, чем при постоянном глобальном значении c=50. Более либеральный хост остается в рабочем состоянии. Снижение общего количества ресурсов для «исправления» строгого хоста также негативно скажется на либеральном хосте, и это не позволит стабилизировать максимум нагрузки на строгом хосте при неудачном порядке обработки запросов.

Решение: отдельно ограничивать одновременную работу по хостам

В части I было добавлено ограничение на скорость начала обработки запросов вместе с общим пулом ресурсов. В части II добавляется ограничение на одновременную работу по отдельным хостам: вложенное ограничение на количество запросов, которые может обрабатывать один конкретный хост.

Напомним три понятия:

  • Глобальная одновременность — размер общего пула ресурсов (например, p-limit(50)). Его устанавливаете вы.
  • Одновременность по хостам — показатель, который фактически наблюдается у конкретного хоста (peak_inflight). Его измеряют.
  • Лимит на хост — отдельный максимальный показатель, который вы устанавливаете для количества одновременных запросов, которые может обрабатывать тот или иной хост, входящий в глобальный пул.
  • Если такой ограничитель на уровне хоста отсутствует, любой свободный глобальный слот может быть занят любыми URL, которые находятся в состоянии готовности, что может привести к перегрузке одного конкретного хоста. Введение вложенного лимита позволяет каждому хосту иметь собственный барьер в очереди:

    async function runPooledFetch(urls, concurrencyLimit, agent, { perHostLimit } = {}) {
      const globalLimit = pLimit(concurrencyLimit);
      const hostLimiters = new Map();
      async function fetchWithLimits(url, execute) {
        return globalLimit(async () => {
          if (perHostLimit && perHostLimit >= 1) {
            const host = new URL(url).hostname;
            if (!hostLimiters.has(host)) hostLimiters.set(host, pLimit(perHostLimit));
            return hostLimiters.get(host)(execute);
          }
          return execute();
        });
      }
      // ...
    }
    

    Теперь группа URL, требующих строгого обработчика, не может одновременно занять все глобальные слоты. Хост с более либеральными требованиями всё ещё может использовать оставшуюся пропускную способность. Запреты связаны с тем параметром лимитирования, который вы хотели контролировать.

    Почему общий пул рабочих процессов концентрирует нагрузку на одном хосте

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

    Практический чек-лист

    • Рассматривайте скорость запуска как отдельную настройку. Семафор — это не ограничитель RPS. Добавьте явное ограничение скорости запуска наряду с ограничением одновременности.
    • Рассматривайте одновременность на каждом хосте как отдельную настройку. Для рабочих нагрузок с несколькими хостами требуется ограничитель на каждого хоста внутри общего пула.
    • Фиксируйте значения observed_start_rps и peak_inflight для каждого хоста. Невозможно определять ограничения, которые вы никогда не измеряли.
  • Оптимизируйте полезную пропускную способность. Оценивайте показатель ok-per-second, а не completed-per-second; даже быстрые ответы 429 считаются сбоями.
  • Точные пороговые значения в ходе одной работы блога не являются переносимыми: механизмы ограничения зависят от состояния системы, времени, истории трафика и репутации IP-адреса. Переносимым решением является изоляция. Сбой, кажущийся проявлением «чрезмерной одновременности», может быть связан с проблемами скорости запуска, планирования по хостам или с обеими причинами одновременно. Пока эти факторы не будут разделены, сокращение количества ресурсов устраняет лишь симптом — и зачастую неверный.

    Анализ метрик без самообмана

    Количество завершённых запросов в секунду растёт каждый раз, когда клиент может быстро открывать сокеты — даже в тех случаях, когда большинство ответов являются отказами. Панели управления, показывающие только показатели одновременной работы без фильтрации успешных запросов, будут рекомендовать неблагоприятные настройки. Сопоставляйте каждую диаграмму пропускной способности с коэффициентом успешных запросов, а по возможности — с количеством сохранённых байтов полезного контента. Если ваша система повторяет попытки из-за ошибок 429, считайте эти попытки отдельно, чтобы всплеск повторных запросов не выглядел как продуктивный параллелизм.

    Логи начала работы должны фиксировать временную метку каждого принятого запроса, а не только каждого завершённого. На основе принятых запросов можно восстановить пиковую нагрузку в первые 100–500 мс, именно в этот период многие клиенты выглядят одинаково, независимо от установленного размера пула. Если две конфигурации имеют одинаковый пик начала работы, не удивляйтесь, если у них будет одинаковая закономерность блокировок.

    Прокси, репутация и честность относительно компромиссов

    Прокси для жилых сетей или центров обработки данных перераспределяют идентичности. Они могут преобразовать поток данных с одним IP-адресом в несколько более тихих потоков. Это способствует проектам сбора данных и позволяет скрыть слабый контроль доступа от целевых систем, устанавливающих ограничения по IP-адресам. Однако это не снимает этических и контрактных обязанностей по отношению к сайтам, с которых вы загружаете данные, а также не устраняет техническую необходимость понимания своего собственного клиента. В первую очередь старайтесь контролировать скорость передачи данных, когда вы управляете клиентом; используйте прокси только тогда, когда продукту действительно требуется более высокая общая пропускная способность через несколько идентичностей — при этом постоянно измеряйте нагрузку на каждую идентичность и каждый хост, чтобы не действовать вслепую за слоем прокси.

    Сочетание обеих частей

    Роботы для обработки контента обычно требуют обоих механизмов регулирования: глобального пула для обеспечения безопасности ресурсов с вашей стороны, лимита начальной скорости для умеренности при подключении, а также лимитов на каждый хост в случае, когда несколько целей используют один и тот же пул. Игнорирование любого из этих механизмов вновь приводит к ситуации, которая в кодах состояния HTTP «выглядит» как конкурентность. Решение этой проблемы не является чем-то мистическим; это использование инструментов анализа в сочетании с вторым ограничивающим механизмом, настроенным на ту переменную, которую вы фактически наблюдали.

    Принципы проектирования, обеспечивающие справедливое сравнение

    Сохраняйте идентичность таксономии успеха при всех испытаниях: коды ok HTML, HTTP 429, другие коды 4xx/5xx, ситуации тайм-аутов и ошибки парсинга должны маркироваться одинаково при каждом запуске. Изменяйте только ту переменную, которая находится на тестировании — размер пула, минимальный интервал начала работы, включение/исключение прокси или порядок очереди. По возможности используйте предварительно загруженные DNS-записи и TLS-соединения, чтобы первые точки графика не были искажены шумом от процесса установления соединения, если только явное начало работы с нуля не является частью тестовой схемы.

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

    URL-адреса фикстчеров должны быть стабильными. Названия страниц вики и страниц каталога сандбокса, которые исчезают в процессе анализа, искажают показатели количества корректных данных. Храните списки фиксчеров в системе контроля версий рядом с остальными элементами, как это делается в проекте concurrency-trap-bench, чтобы графики отражали известные входные данные.

    Как выглядит «полезная пропускная способность» в конвейере обработки

    Системы нижнего уровня интересуются принятыми документами, а не количеством сетевых запросов. Если скрейпер передает данные индексатору, считайте количество индексированных документов в минуту. Если он передает данные в базу цен, считайте количество проверенных записей. Соотносите цели оптимизации с задачами соответствующего бизнес-подразделения. В противном случае инженеры будут максимизировать косвенные показатели — количество завершённых HTTP-запросов, — которые включают огромное количество ответов с кодом 429.

    Повторные попытки негативно влияют на системы с неуправляемой загрузкой. Всплеск запросов, приводящий к блокировкам, сразу же последующие повторные попытки могут ещё больше увеличить скорость запуска. Необходимо снижать частоту запросов при коде 429 с использованием механизма джиттера, соблюдать значение Retry-After, если оно указано, и ни в коем случае не позволять атакам повторных попыток обойти ограничения скорости запуска. Контроллер приёма запросов должен рассматривать повторные попытки как новые запуски.

    Расписатели для нескольких арендаторов и хостов

    Сервисы, обрабатывающие запросы от множества клиентов, часто уже имеют глобальные лимиты на одновременную работу процессов в целях обеспечения безопасности. Тем не менее им всё равно требуются лимиты по отдельным пунктам назначения, чтобы список URL одного клиента не мог монополизировать ресурсы уязвимого источника. Глобальные, локальные по арендатору и по хосту ограничения должны дополнять друг друга. Их следует реализовывать в виде отдельных уровней контроля, а не рассчитывать на справедливую очередь на основе одного семафора.

    Когда задержки хостов отличаются в несколько порядков, пулы с механизмом перераспределения нагрузки склонны отдавать предпочтение быстрому хосту. Такая склонность способствует лучшей загрузке ресурсов, но вредит медленному хосту, когда его URL-адреса наконец становятся доступными для обработки. Лимиты на количество запросов к каждому хосту снижают ущерб; факультативные веса для каждого хоста могут дополнительно отражать правила вежливого обращения.

    Интерпретация результатов работы прокси без чрезмерных предположений

    Если прямой выход запроса не удается, а выход через прокси происходит успешно при идентичных настройках пула, скорее всего, целевой сервер проверяет идентичность сети при обработке запроса. Это полезная операционная информация. Однако это не доказательство того, что физика начальной скорости обработки запросов изменилась. За прокси всё равно следует вести учет запросов по каждой идентичности выхода и по каждому целевому хосту. В противном случае вы лишь перемещаете точку слепоты.

    Политики соблюдения правил и использования роботов по-прежнему должны соблюдаться вами. Более высокая общая пропускная способность через множество выходов увеличивает зону поражения логических ошибок. Регулировка активации функций и установление лимитов на один хост сохраняются даже при включенных прокси, что позволяет снизить интенсивность работы без необходимости перенастраивать инфраструктуру идентификации.

    Закрытие цикла от экспериментов к стандартным настройкам

    Как только измерения покажут, что скорость запуска и пиковые значения на один хост лучше предсказывают возможность банов, чем только общий размер пула, эти данные следует закодировать в качестве стандартных настроек в библиотеке клиента: требовать параметр RPS (или минимального интервала) и устанавливать лимиты на один хост в режимах с несколькими хостами, а также экспортировать метрики для обоих случаев. В документации должны быть показаны как негативные показатели (рост количества завершенных запросов при снижении числа успешных) так и положительные. Обучение команды особенностям возникновения сбоев предотвращает ситуацию, когда команда «исправляет» проблемы с одновременной обработкой запросов, что приводит к незаметному потере полезных данных.

    Чистая реализация эксперимента с частотой запуска

    Фиксируйте версии Pin Node и undici (или вашего HTTP-стека), чтобы поведение пулинга соединений оставалось сопоставимым. Во время проведения тестов отключайте ненужные промежуточные компоненты, имитирующие поведение браузера и обеспечивающие попытки повторной отправки запросов. Очищайте кэш DNS между прямыми и проходящими через прокси запусками, если ваш инструментарий разрешает домены по-разному. Записывайте общее время выполнения всей серии запросов, а также времена начала и окончания каждого отдельного запроса, чтобы можно было проанализировать количество обработанных запросов в первые полсекунды — период, когда клиенты из пула выглядят идентично, независимо от установленного уровня одновременных операций.

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

    Интерпретация строгости в стиле Arch Wiki

    Сервисы публичной документации различаются: некоторые ограничивают доступ по IP-адресу и пути, другие — по User-Agent, ещё другие — по количеству одновременных соединений или по частоте запросов в течение определённого временного интервала. Сегодняшний статус 429 может завтра смениться более слабым замедлением из-за изменения правил оператора. Поэтому цифры, приведённые в статье, представляют собой иллюстративные примеры, а не вечные константы. Важна методология: выделяются размер пула, скорость приёма запросов и пиковые значения для каждого хоста, после чего меняется по одной переменной за раз.

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

    Подробности реализации ограничения начальной скорости

    Минимальный интервал, определяемый максимальным числом запросов в секунду, прост в реализации и эффективен для клиентов с одним процессом. Кошики токенов позволяют осуществлять кратковременные всплески нагрузки при одновременном соблюдении долгосрочных средних показателей — это удобно, когда нужны быстрые интерактивные запросы, но при этом необходимо соблюдать умеренную скорость при массовом сканировании. Механизм «утекающих» кошиков сглаживает более резкие колебания нагрузки. Какой бы алгоритм вы ни выбрали, применяйте его к моменту начала выполнения запросов, включая попытки их повтора. Наплыв попыток повтора, обходящих ограничения, воссоздает ситуацию массовых запросов, возникшую в момент t=0 после первого запрета.

    Многопроцессные сканеры требуют распределенного механизма блокировки доступа (Redis, etcd или централизованный планировщик). Одних лишь локальных ограничений недостаточно для координации работы пяти рабочих процессов, каждый из которых считает, что может отправлять по два запроса в секунду.

    Подробности реализации ограничений на один хост

    Обычным подходом является использование вложенных семафоров, ключами к которым служат имена хостов: сначала берется глобальный семафор, затем семафор хоста, после чего происходит получение данных, и в конце — освобождение ресурсов в обратном порядке. Необходимо определить, делит ли www один и тот же ключ с apex. Также нужно решить, как перенаправления, изменяющие количество хостов, влияют на установленные лимиты. Шарды одного сайта, определяемые по пути, обычно все равно делят один бюджет хоста, если только нет явных указаний от CDN в противном направлении.

    Выпускайте метрики: inflight_global, inflight_per_host{host}, admissions_per_second, http_429_total{host}. Устанавливайте оповещения при росте количества запросов с кодом 429, а не только при исчерпании лимитов на ошибки кода 5xx.

    Порядок очередей в производственных планировщиках

    Порядок карты сайта, алгоритм BFS от исходной точки, очереди с приоритетом для «важных» URL и очереди повторных попыток — всё это изменяет структуру пиков нагрузки на отдельных хостах. Очередь повторных попыток, в которую добавляются неудачные URL Arch, может случайно воссоздать порядок блоков после частичного сбоя. Справедливое распределение заданий между хостами — очереди с методом круговой ротации на каждом хосте — снижает случайную концентрацию нагрузки ещё до введения жестких ограничений. Жесткие ограничения по-прежнему необходимы, когда одного лишь принципа справедливости недостаточно для контроля пиков при неравномерной задержке.

    Прокси без самообмана

    Домашние сети меняют распределение идентификаторов. Они не отменяют законы физики: если каждый идентификатор по-прежнему отправляет по пять запросов одновременно, серверы, которые определяют цели по поведению, а не по IP-адресу, могут всё равно запретить доступ. Фиксируйте записи о входах по каждому идентификатору выхода. Переменяйте их аккуратно. Соблюдайте правила, предусмотренные для роботов, и условия контракта. Даже при включенных прокси предпочитайте умеренный темп работы, чтобы ошибка не привела к широкомасштабному сбою.

    Прокси в центрах обработки данных дешевле и проще для идентификации; прокси для домашних пользователей стоят дороже и связаны с этическими проблемами. Выбирайте осознанно; не считайте «прокси» синонимом решения проблем.

    Встроите два необходимых параметра в общие обертки HTTP-клиентов, используемые краулерами: maxInFlight и maxStartsPerSecond, а также maxInFlightPerHost при наличии нескольких хостов. Не создавайте клиент для режима работы с несколькими хостами без ограничений на количество запросов к каждому из них. Предоставляйте шаблоны панелей управления, отображающие количество успешных запросов в секунду. Обучайте пользователей с использованием двух графиков: один показывает высокую пропускную способность при одновременных запросах, а другой — потерю полезных страниц.

    • Устанавливайте ограничения на количество начинаемых запросов, а не только на активные в данный момент.
    • Устанавливайте ограничения на количество запросов к каждому хосту в рамках общего пула.
    • Измеряйте количество принимаемых запросов и пиковую нагрузку на каждый хост при каждом запуске.
  • Оценивайте успех по полезности страниц, а не по количеству завершённых соединений.
  • Применяйте механизмы контроля доступа к попыткам повторной отправки запросов.
  • При смешанных тестах используйте более гибкий контрольный хост.
  • Считайте успех прокси изменением репутации, а не доказательством нормальной работы системы планирования.
  • Пересматривайте цифры по мере изменения правил работы системы; сохраняйте методологию.
  • Пока эти привычки не войдут в обычай, команды будут продолжать «исправлять проблемы с одновременными запросами», что приводит к менее заметным формам сбоев, при которых всё равно возвращается код HTTP 429 и тратятся бюджеты на обработку запросов.