Strona główna / Artykuły / Dlaczego zwiększanie współbieżności powoduje błędy HTTP 429: szybkość rozpoczynania i limity na poziomie hosta

Dlaczego zwiększanie współbieżności powoduje błędy HTTP 429: szybkość rozpoczynania i limity na poziomie hosta

Zwiększanie liczby pracowników bez określenia szybkości rozpoczynania pracy oraz limitów na hosta powoduje nagłe wzrosty obciążenia, które wywołują błędy 429. Przepustowość osiąga się dzięki kontrolowanej równoczesności, mechanizmom opóźniania oraz rozsądnemu projektowi kolejki.

3571 słów

TL;DR: Zwiększanie współbieżności jest przedstawiane jako darmowy paralelizm: więcej procesorów, więcej zapytań, więcej danych. To brzmi dobrze, więc dwadzieścia musi być lepsze, a pięćdziesiąt nieodparte. Problem polega na tym, że zwiększenie liczby zasobów nie tylko podnosi współbieżność. Określa ono również intensywność żądań kliencka skierowanych do serwera od samego początku, a w przypadku obciążeń wieloserwerowych – jaka współbieżność na serwer powstanie bez żadnych ustawień. Gdy te ukryte mechanizmy zawiodą, kod stanu to często HTTP 429, który oznacza dokładnie „zbyt wiele jednoczesnych zapytań”. Zespoły wtedy zmniejszają liczbę zasobów lub dodają proxy, by kontynuować pracę. Nie identyfikując prawdziwej przyczyny, tracą możliwość uzyskania lepszej wydajności.

Część I — Współbieżność vs szybkość rozpoczynania: jedna ustawienie puli kontroluje dwa limity

Rozmiar puli pracowników (p-limit(n), semafor, liczba wątków) określa, ile operacji może być w trakcie wykonywania. Nie ogranicza natomiast szybkości rozpoczynania nowych zadań. W momencie t=0 pula z pięćdziesięcioma czekającymi zadaniami może rozpocząć pięćdziesiąt żądań w jednym tiku – co powoduje gwałtowny wzrost szybkości rozpoczynania zadań – mimo że w stanie równowagi konkurencja będzie wyglądać „tylko” na pięćdziesiąt zadań. Limitery szybkości przejmują się tym nagłym wzrostem tak samo jak utrzymywanym równoległością.

Mierzenie nagłego wzrostu szybkości rozpoczynania zadań

Pomaga narzędzie umożliwiające powtarzalne testy. Weź małą kolekcję adresów URL artykułów z Arch Linux Wiki:

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

Stwórz obciążenie składające się z 100 żądań, przeglądając listę:

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)))
  );
  // ...
}

Zainstaluj z różnymi rozmiarami buforów, sklasyfikuj każdy wynik jako poprawny lub 429 (oraz inne błędy), a także zapisuj liczbę ukończonych żądań na sekundę wraz z liczbą poprawnych wyników. Ta różnica jest istotna: szybki błąd 429 nadal wpływa na metryki iluzoryczne „ukończonych żądań/s”, mimo że strona nie jest dostarczana.

Korzystna przepustowość spada wraz ze wzrostem globalnej współbieżności

Przy stałym obciążeniu 100 zapytań, przy którym zmienia się jedynie rozmiar puli, połączenie bezpośrednie daje słabe wyniki. Przy współbieżności 1 praktycznie wszystkie 100 zapytań jest udanych, a liczba zakończonych zapytań na sekundę wynosi około 2,4/s – trzeba czekać na pełne renderowanie bez możliwości równoległego uruchamiania zadań. Przy współbieżności 10 liczba użytecznych danych drastycznie spada: udanych jest około 42 stron, a 58% zapytań zwraca błąd 429, przy czym liczba zakończonych zapytań na sekundę wzrasta do około 23,5/s. Przy współbieżności 25 i 50 liczba udanych zapytań nadal spada (około 24/100, potem 16/100), natomiast liczba zakończonych zapytań na sekundę pozostaje na poziomie około dwudziestu kilku (18,2/s i 21,8/s).

Punkt szczytowy zużycia to współbiegłość 10 przy 23,5 zakończonych żądania/s — najwyższa w tabeli — połączona z jedną z najgorszych liczb odpowiedzi typu „ok”. Optymalizacja pod kątem zakończonych żądań na sekundę polegałaby na wyborze ustawienia, które zużywa najwięcej zasobów na blokowanie. Praktyczną wydajnością jest liczba odpowiedzi typu „ok” na sekundę, a nie sama liczba zakończonych żądań.

Ustalenie ograniczenia szybkości rozpoczynania żądań przy tej samej współbiegłości naprawia sytuację

Zachowaj niezmieniony rozmiar puli i określ, jak szybko kolejne żądanie może zostać rozpoczęte. Minimalną przerwę można obliczyć na podstawie maksymalnej liczby żądań na sekundę:

// 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;
}

Dzięki takiej kontrolie ta sama współbiegłość, która wcześniej powodowała przeciążenie serwera, może obsłużyć niemal wszystkie strony, ponieważ nagły napływ żądań znika. Prawidłowy model myślowy zakłada dwie ustawienia: współbiegłość (żądania w trakcie przetwarzania) oraz szybkość rozpoczynania żądań (liczba przyjmowanych żądań na sekundę). Połączenie ich w jedną wartość ukrywa mechanizm awarii.

Dlaczego klient HTTP typu pool wysyła serię zapytań w momencie t=0

Puły nie znają zasad uprzejmego tempa działania. Znają natomiast dostępne sloty. Podczas uruchamiania każdy slot jest wolny, więc każde zadanie w kolejce, które może zostać wykonyane, faktycznie jest realizowane. Ponowne używanie połączeń oraz multiplexing w protokole HTTP/2 mogą sprawić, że ta seria zapytań będzie jeszcze intensywniejsza w transmisji. Tylko kontroler dopuszczeń – taki jak bufor tokenów, zasada minimalnej przerwy czy bufor przeciekowy – może kształtować moment rozpoczęcia działania niezależnie od ograniczeń obowiązujących podczas transmisji.

Czy proxy domowe naprawia problem gwałtownego wzrostu szybkości rozpoczynania zapytań?

Zasady kontrolowania tempa działania służą do zapewnienia poprawności: zbieranie danych bez wywoływania nagłych wzrostów obciążenia celu. Rzeczywistość inżynieryjna czasami wymaga szybkości, której nie może zapewnić ograniczenie 2,4 zapytania na sekundę. Ten sam prosty model – brak ograniczeń szybkości rozpoczynania, gwałtowne wysyłanie zapytań w momencie t=0 – pośredniczone przez proxy domowe może zapewnić mniej więcej 98–99/100 sukcesów praktycznie bez żadnych blokad na różnych poziomach jednoczesności, podczas gdy bezpośrednia ścieżka nadal napotyka poważne problemy.

To nie oznacza, że proxy’i eliminują konieczność zrozumienia szybkości rozpoczynania połączeń. Zmieniają reputację IP oraz to, w jak agresywny sposób strona przypisuje ruch. Mogą maskować nagłe wzrosty obciążenia, które mogłyby doprowadzić do zablokowania konkretnego IP wyjściowego. Jeśli wymaganiem produktu jest „zbieranie danych bez wyglądania na falę ataków”, kontrola tempa pozostaje głównym narzędziem; proxy’i stanowią warstwę związaną z przepustowością i reputacją, a nie zamiennik dla pomiarów liczby połączeń.

Budowa agenta proxy’owego zazwyczaj pobiera dane uwierzytelniające z otoczenia:

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);

Należy je używać celowo, rejestrować, czy działanie odbyło się bezpośrednio, czy za pośrednictwem proxy’a, a także zapisywać zaobserwowaną szybkość rozpoczynania połączeń, aby wiedzieć, który element wpłynął na wynik.

Część II — Jednoczesność na poziomie hosta

Pooli globalne ukrywają również drugą, powstającą automatycznie ograniczającą się zasadę. Rozważmy:

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

Pięćdziesiąt globalnych slotów rozdzielonych między wiele hostów nie oznacza, że każdy z nich ma do dyspozycji co najwyżej pięćdziesiąt z nich w czasie rzeczywistym. Kolejność w kolejce oraz różnice w czasie odpowiedzi decydują o tym, ile żądań faktycznie może obsłużyć dany host. Lista mieszana może wyglądać zrównoważona na papierze:

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

jednak nadal może powodować nagłe wzrosty obciążenia na konkretnym hostie, gdy jego URL-y znajdują się w zbiorze dostępnych do uruchomienia.

Pomiary współbieżności na poziomie poszczególnych hostów

Wykorzystaj ten zestaw do testowania z dwoma hostami:

  • Host restrykcyjny — Arch Linux Wiki (10 URL-ów artykułów), który blokuje się pod obciążeniem, tak jak w Części I.
  • Host łagodniejszy — strony katalogowe books.toscrape.com (10 URL-ów), które rzadko blokują się i służą jako kontrola. Jeśli środowisko testowe zawiedzie, oznacza to błąd w kliencie.

Lista przemienna daje 20 adresów URL × 5 powtórzeń = 100 zapytań przy globalnej współbieżności 50. Listy elementów i narzędzia do ich tworzenia znajdują się w repozytorium open concurrency-trap-bench (na przykład 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

Zadania uporządkowane mogą być realizowane metodą round-robin, blokować według hosta lub być przetasowywane za pomocą seedu:

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;
}

Mierz szczytową liczbę zadań w trakcie wykonywania na każdym hostzie na podstawie zdarzeń startu i zakończenia:

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
}

Globalna współbieżność pozostaje niezmienna; zmienia się tylko kolejność. Dzięki temu presja na poszczególne hosty jest oddzielona od wielkości puli zasobów.

Jak kolejność w kolejce zmienia współbieżność na poszczególnych hostach

Prawdziwe narzędzia do przeglądania stron nigdy nie stosują idealnej metody round-robin przez cały czas. Mapy strony, grafy zależności oraz kolejki do ponownych prób przetasowują zadania. Dlatego identyczne ustawienia globalne mogą prowadzić do różnych szczytowych wartości na poszczególnych hostach.

1. Round-robin: równomierne przetwarzanie hostów po kolei

Lista naprzemienna (A B A B …) rozkłada pracę. Szczyt liczby zadań w trakcie wykonywania na ścisłym hostingu pozostaje stosunkowo umiarkowany, ponieważ drugi host ciągle zajmuje wolne sloty.

2. Blokowanie: grupowanie zapytań od tego samego hosta

Grupowanie wszystkich adresów URL typu Arch, a następnie wszystkich adresów URL książek, daje ścisłemu hostowi dłuższy okres ciągłego wykonywania zadań. Szczyt liczby zadań w trakcie wykonywania na tym hostingu rośnie wraz z wielkością globalnego puli, mimo że „jednoczesność nadal wynosi 50”.

3. Mieszanie (ziarno 42): losowy porządek

Mieszanie z użyciem ziarna znajduje się pomiędzy skrajnościami i odzwierciedla przypadkowy porządek w produkcji. Szczyty zmieniają się w zależności od ziarna; lekcja polega na wrażliwości na te czynniki, a nie na magicznej permutacji.

Ta sama globalna jednoczesność, różne obciążenia na poszczególnych hostach

W tych wzorcach wskaźnik poprawności na ścieżce surowego hosta osiąga swój maksymalny poziom w trakcie przetwarzania z większą dokładnością niż stała wartość globalna c=50. Host łagodniejszy pozostaje sprawny. Obniżenie globalnego puli w celu „naprawy” surowego hosta skutkowałoby również negatywnymi konsekwencjami dla hosta łagodniejszego i nadal nie pozwoliłoby ustabilizować maksymalnego poziomu surowego hosta przy niekorzystnym uporządkowaniu.

Rozwiązanie: oddzielne ograniczenie liczby równoczesnych zapytań na hosta

Część I wprowadziła ograniczenie szybkości rozpoczynania zapytań obok puli. Część II dodaje ograniczenie na poziomie poszczególnych hostów obok puli: wewnętrzne ograniczenie dotyczące liczby zapytań w trakcie przetwarzania, jakie może mieć dany host.

Pamiętajmy o trzech pojęciach:

  • Globalna równoczesność — wielkość wspólnej puli (na przykład p-limit(50)). To Ty to ustawiasz.
  • Równoczesność na poziomie hosta — to, co faktycznie widzi dany host (peak_inflight). To Ty to mierzysz.
  • Ograniczenie na jeden host — odrębna maksymalna wartość, którą sam wybierasz dla liczby jednoczesnych połączeń, jakie może obsłużyć dany host, umieszczona w ramach globalnego puli.
  • Jeśli brakuje tego ograniczenia na poziomie hosta, każde wolne miejsce w globalnej puli może zostać zajęte przez dowolne URL-y, które są gotowe do użycia — co może prowadzić do przeciążenia jednego konkretnego hosta. Dodanie ograniczenia na poziomie hosta zapewnia każdemu hostowi własną bramkę w kolejce:

    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();
        });
      }
      // ...
    }
    

    Teraz klastrum URL-ów z surowymi ograniczeniami nie może jednocześnie zajmować wszystkich miejsc w globalnej puli. Hosty o łagodniejszych warunkach nadal mogą korzystać z pozostałej przepustowości. Zabrony są powiązane z zmienną ograniczeniową, którą chciałeś kontrolować.

    Dlaczego wspólna pula pracowników koncentruje obciążenie na jednym hostie

    Baza wypełnia wolne miejsca tym, co może być uruchomione. Szybkie serwery szybko zwalniamy wolne miejsca i przejmują więcej własnych zadań — aż grupa adresów URL serwerów o wysokiej wydajności stanie się w stanie wspólnego uruchomienia i odziedziczy dodatkową przepustowość. Wielkość tej dodatkowej przepustowości zależy od kolejności, opóźnień i składu zadań — żadne z tych elementów nie jest uwzględniane w pojedynczej wartości liczbowej dotyczącej jednoczesności. Dlatego limiter dla poszczególnych serwerów jest lepszy niż ślepe zmniejszanie globalnej bazy zasobów.

    Praktyczna lista kontrolna

    • Traktuj szybkość rozpoczynania zadań jako odrębną ustawienie. Semafor nie jest limiterem RPS. Dodaj wyraźny limit szybkości rozpoczynania obok ustawienia dotyczącego jednoczesności.
    • Traktuj jednoczesność dla poszczególnych serwerów jako odrębną ustawienie. Zadania wymagające wielu serwerów potrzebują limitera dla każdego z nich w ramach globalnej bazy zasobów.
    • Zapisuj wartości observed_start_rps oraz peak_inflight dla każdego serwera. Nie można analizować limitów, których nigdy nie zmierzyło się.
  • Optymalizuj użyteczną przepustowość. Licz się z wynikiem w określonej liczbie operacji na sekundę, a nie z liczbą ukończonych zadań na sekundę; szybkie błędy 429 nadal są uznawane za niepowodzenia.
  • Dokładne progi w ramach jednego procesu na blogu nie są przenośne: mechanizmy ograniczające działają w oparciu o stan aplikacji i zależą od czasu, historii ruchu oraz reputacji IP. Przenośnym rozwiązaniem jest izolacja. Błąd wyglądający na „zbyt dużą jednoczesność” może być spowodowany problemem z szybkością rozpoczynania zadań, problemem planowania dla poszczególnych hostów lub oboma tymi czynnikami. Dopóki te zmienné nie zostaną oddzielone, zmniejszanie liczby zasobów leczy jedynie objaw – a często niewłaściwy.

    Czytanie metryk bez oszukiwania samego siebie

    Liczba zakończonych żądań na sekundę rośnie wtedy, gdy klient potrafi szybko otwierać sockety – nawet jeśli większość odpowiedzi to odrzucenia. Panel sterowania, które promują pracę w warunkach wysokiej jednoczesności bez filtrowania pozytywnych odpowiedzi, będą zalecać nieodpowiednie ustawienia. Do każdego wykresu przepustowości należy dodać wskaźnik pozytywnych odpowiedzi, a jeśli to możliwe, również ilość zapisanych użytecznych danych. Jeśli Twój proces powtarza próby z błędem 429, licz te próby oddzielnie, aby fala powtórzeń nie wyglądała jak produktywny paralelizm.

    Zapisy dotyczące szybkości rozpoczynania połączeń powinny zawierać datę i godzinę każdego przyjęcia żądania, a nie tylko jego zakończenia. Na podstawie tych danych można odtworzyć nagłe wzrosty aktywności w pierwszych 100–500 ms, kiedy to wiele klientów wygląda identycznie, niezależnie od ustalonej wielkości puli. Jeśli dwie konfiguracje charakteryzują się takim samym nagłym wzrostem aktywności przy otwieraniu połączeń, nie dziw się, że będą miały ten sam wzorzec blokad.

    Proxy, reputacja i szczerość co do kompromisów

    Proxy do użytku domowego lub w centrach danych przekierowują identyfikację. Mogą przekształcić zbieg żądań pochodzących z jednego IP adresu w wiele cichszych strumieni danych. To ułatwia projekty zbierania informacji i może ukryć słabe mechanizmy kontroli dostępu przed destynacją, która narzuca ograniczenia dotyczące IP adresów. Nie eliminuje to jednak obowiązków etycznych i umownych wobec stron, od których pobierasz dane, ani potrzeby inżynieryjnej polegającej na zrozumieniu własnego klienta. Najpierw staraj się kontrolować tempo przesyłania danych, jeśli masz wpływ na klienta; używaj proxy tylko wtedy, gdy produkt naprawdę wymaga wyższej łącznej przepustowości przy wielu identyfikatorach — i nieprzerwanie mierz nacisk na poziomie poszczególnych identyfikatorów i hostów, aby nie działać ślepo za warstwą proxy.

    Łączenie tych dwóch elementów

    Boty produkcyjne zazwyczaj wymagają obu rodzajów ograniczeń: globalnego zasobu zapewniającego bezpieczeństwo z Twojej strony, limitu szybkości startu dla uprzejmości podczas przyjmowania żądań, oraz ograniczeń na poziomie poszczególnych hostów, gdy kilka destynacji korzysta z tego samego zasobu. Pominięcie któregokolwiek z tych ograniczeń powoduje powstanie sytuacji awaryjnej, która w kodach stanu HTTP „wygląda jak konkurencja”. Rozwiązanie tego problemu nie jest tajemnicze – polega na użyciu narzędzi diagnostycznych oraz drugiego ogranicznika skierowanego na zmienną, którą faktycznie zaobserwowałeś.

    Wskazówki projektowe dotyczące wykorzystania narzędzi, które zapewniają sprawiedliwe porównania

    Zachowaj identyczną taksonomię sukcesu we wszystkich testach: kod OK, HTTP 429, inne błędy typu 4xx/5xx, przekroczenia czasowe oraz błędy parsowania powinny być oznaczane w ten sam sposób przy każdym uruchomieniu. Zmieniaj tylko badaną zmienną – rozmiar puli, minimalną przerwę startową, ustawienie proxy włączone/wyłączone lub kolejność w kolejce. Jeśli to możliwe, przygotuj wcześniej DNS i TLS, aby pierwsze punkty krzywej nie były zdominowane przez szumy związane z chłodnym nawiązywaniem połączenia, chyba że chłody start jest wyraźnie częścią badania.

    Powtarzaj testy wystarczającą liczbę razy, aby zredukować szumy, ale nie tak często, by adaptacyjny limiter strony trwale zmieniał swoje ustawienia w trakcie eksperymentu. Gdy limitery przechowują stan, zanotuj godzinę dnia oraz to, czy wcześniejsze blokady mogą nadal oddziaływać. Opublikuj wartości początkowe użyte do losowania, aby inni mogli odtworzyć efekty kolejności.

    Adresy URL powinny być stabilne. Tytuły w wiki oraz strony katalogowe w środowisku testowym, które znikają w trakcie analizy, psują liczbę poprawnych rekordów. Zapisz listy elementów do kontrolowania wersji obok zestawu narzędzi, tak jak robi projekt concurrency-trap-bench, aby wykresy odnosiły się do znanych danych wejściowych.

    Jak wygląda „przydatna przepustowość” w łańcuchu przetwarzania

    Systemy poniżej w łańcuchu interesują się akceptowanymi dokumentami, a nie liczbą prób połączeń. Jeśli skrapper dostarcza dane do indeksatora, licz dokumenty zindeksowane na minutę. Jeśli dostarcza dane do bazy cen, licz zweryfikowane rekordy. Ustal cele optymalizacji zgodnie z wymaganiami danej jednostki biznesowej. W przeciwnym razie inżynieria będzie maksymalizować metrykę zastępczą – ukończone wymiany HTTP – która obejmuje ogromną ilość odpowiedzi typu 429.

    Próby ponownego uruchomienia źle wpływają na zasoby bez ustalonego tempa. Nagły wzrost obciążenia prowadzący do zablokowań, po którym następują natychmiastowe próby ponownego uruchomienia, może jeszcze bardziej zwiększyć szybkość rozpoczynania operacji. Należy zmniejszać częstotliwość prób w przypadku błędu 429 z dodatnią odchylką, przestrzegać wartości Retry-After, gdy jest ona określona, i nigdy nie pozwalać, by fale prób obejшły ograniczenia szybkości rozpoczynania. Kontroler dopuszczania musi traktować próby ponownego uruchomienia jako nowe rozpoczęcia operacji.

    Planery dla wielu użytkowników i hostów

    Usługi obsługujące wiele klientów często mają już ustalone globalne limity jednoczesności dla bezpieczeństwa procesów. Nadal potrzebują jednak budżetów na poziomie poszczególnych destynacji, aby lista adresów URL jednego klienta nie mogła zmonopolizować wrażliwego źródła. Limity warstwowe – globalne, dla poszczególnych użytkowników i hostów – muszą być ze sobą skomponowane. Należy je wdrożyć jako odrębne warstwy, zamiast liczyć na to, że sprawiedliwe kolejki powstaną dzięki pojedynczemu semaforowi.

    Gdy serwery różnią się o kilka rzędów wielkości pod względem opóźnienia, grupy do dzielenia pracy skłaniają się ku szybszemu serwerowi. Taka tendencja jest korzystna dla wykorzystania zasobów, ale szkodliwa dla wolniejszego serwera, gdy jego adresy URL w końcu staną się dostępne do uruchomienia. Limity na poziomie poszczególnych serwerów ograniczają te szkody; opcjonalne wagi dla poszczególnych serwerów mogą dodatkowo odzwierciedlać zasady uprzejmości.

    Tłumaczenie wyników proxy bez magicznego myślenia

    Jeśli bezpośrednie wysyłanie danych zawodzi, a wysyłanie przez proxy odnosi sukces przy identycznych ustawieniach grupy, cel prawdopodobnie opiera kontrolę na tożsamości sieciowej. To przydatna wiedza operacyjna. Nie jest to jednak dowód na zmianę zasad działania szybkości rozpoczynania transmisji. Za proxyami nadal należy rejestrować próby połączenia według tożsamości wyjścia i poszczególnych serwerów docelowych. W przeciwnym razie jedynie przenosi się to niewidzialne miejsce problemu.

    Zasady zgodności i dotyczące robotów nadal muszą być przestrzegane. Wyższa łączna prędkość przetwarzania poprzez wiele wyjść zwiększa zasięg skutków błędów logicznych. Mechanizmy kontrolne oparte na flagach funkcjonalności oraz limity na poziomie każdego hosta działają nawet przy włączonych proxyach, dzięki czemu można zmniejszyć poziom agresywności bez konieczności ponownego wdrażania infrastruktury tożsamości.

    Zamknięcie pętli od eksperymentów do ustawień domyślnych

    Gdy pomiary wykażą, że szybkość rozpoczynania operacji oraz maksymalne wartości na poziomie każdego hosta lepiej przewidują blokady niż sama ogólna liczba zasobów, należy zapisać te wyniki jako ustawienia domyślne w bibliotece klienta: wymagać parametru RPS (lub min-gap), ustalać górne limity dla każdego hosta w trybach wielohostowych oraz eksportować wskaźniki dla obu przypadków. Dokumentacja powinna pokazywać złą wersję panelu kontrolnego (rosnąca liczba zakończonych żądań przy spadku liczby udanych) obok dobrej. Szkolenie z mechanizmów awarii zapobiega temu, by kolejny zespół „naprawiał” kwestię współbieżności, powodując tym samym ukryte przerwy w dostępie do użytecznych danych.

    Czyste przeprowadzanie eksperymentu z szybkością rozpoczynania

    Zamroź wersje Pin Node i undici (lub Twojej warstwy HTTP), aby zachowanie połączeń typu connection pooling pozostało porównywalne. Wyłącz niepowiązane z tym narzędzia do ponawiania prób typu browser podczas testów. Wyczyść pamięć cache DNS między bezpośrednimi a przekierowanymi testami, jeśli narzędzie rozwiązuje adresy host w inny sposób. Zapisz czas trwania całej serii testów oraz daty rozpoczęcia i zakończenia każdej prośby, aby móc przeanalizować czas przyjęcia zapytań w pierwszej połowie sekundy – to właśnie w tym oknie klienci z puli wyglądają identycznie, niezależnie od ustalonej wartości równoczesności.

    Podczas tworzenia wykresów zawsze pokazuj liczbę udanych zapytań obok liczby ukończonych zapytań na sekundę. Wykresy z dwoma osiami, które ukrywają liczbę udanych zapytań, są przykładem mierzalników pozorowych. Eksportuj pliki CSV z narzędzia, aby inni mogli ponownie przeprowadzić obliczenia bez polegania na zrzutach ekranu.

    Interpretacja surowości w stylu Arch Wiki

    Hosty używane do dokumentacji publicznej różnią się między sobą: niektóre ograniczają przepustowość na podstawie IP i ścieżki, inne – na podstawie User-Agent, jeszcze inne – liczby jednoczesnych połączeń, a jeszcze inne – szybkości wysyłania żądań w określonych przedziałach czasu. Błąd 429 dziś może stać się mniej wyraźnym spowolnieniem jutro w zależności od zmian w polityce operatora. Dlatego liczby podane w artykule stanowią jedynie przykładowe wzorce, a nie wieczne stałe. Kluczowa jest metodyka: oddzielna wielkość puli, szybkość przyjmowania żądań oraz maksymalne obciążenie poszczególnych hostów, po czym zmienia się jedną zmienną po drugiej.

    Jeśli używasz własnego, rygorystycznego hosta, zachowaj w tym samym testie również łagodniejszy host kontrolny. Gdy host kontrolny zawiedzie, twój klient będzie niepracujący. Jeśli awaria dotyczy tylko rygorystycznego hosta, możesz badać wpływ jego polityki na twój harmonogram.

    Szczegóły wdrożenia ograniczeń szybkości startu

    Minimalna przerwa wynikająca z maksymalnej liczby zapytań na sekundę jest prosta i skuteczna dla klientów z jednym procesem. Zbiorniki tokenów umożliwiają krótkie serie zapytań, jednocześnie wymuszając utrzymanie długoterminowych średnich – przydają się, gdy chcesz szybkich interaktywnych pobierania, ale także uprzejmego przeszukiwania dużych ilości danych. Zbiorniki z wyciekami działają bardziej płynnie. Niezależnie od wybranego algorytmu, należy go zastosować do rozpoczynania operacji, włączając powtórki. Burza powtórek, która omija te ograniczenia, odtwarza sytuację z t=0 po pierwszej blokadzie.

    Przeglądarki wieloprocesowe wymagają rozproszonego zamka dostępu (Redis, etcd lub centralnego planeru). Samodzielne lokalne ograniczenia nie pozwolą skoordynować pracy pięciu pracowników, z których każdy sądzi, że może rozpoczynać dwa zapytania na sekundę.

    Szczegóły wdrożenia ograniczeń na poziomie hosta

    Zwykłym wzorcem są zagnieżdżone semafory kluczowane pod kątem nazwy hosta: najpierw uzyskuje się dostęp do semafora globalnego, potem do semafora hosta, następnie pobiera dane, a na końcu uwalnia się zasoby w odwrotnej kolejności. Należy ustalić, czy www i apex dzielą ten sam klucz. Trzeba też określić, w jaki sposób przekierowania zmieniającce liczbę hostów wpływają na dostępne limity. Fragmenty tej samej strony oparte na ścieżkach zazwyczaj nadal dzielą jeden budżet hosta, chyba że istnieją wyraźne dowody od CDN wskazujące inaczej.

    Należy publikować następujące metryki: inflight_global, inflight_per_host{host}, admissions_per_second, http_429_total{host}. Należy wysyłać alerty, gdy wzrasta liczba błędów 429, a nie tylko wtedy, gdy wyczerpują się limity błędów typu 5xx.

    Kolejność w kolejkach w planerach produkcyjnych

    Kolejność mapy strony, algorytm BFS od punktu wyjścia, kolejki priorytetowe dla „ważnych” adresów URL oraz kolejki ponownych prób wszystkie przekształcają szczyty obciążenia na poszczególnych serwerach. Kolejka ponownych prób, która umieszcza na początku nieudane adresy Arch, może przypadkowo przywrócić pierwotny porządek po częściowym awarii. Sprawiedliwe zarządzanie kolejkami pomiędzy serwerami – kolejki typu round-robin dla każdego serwera – zmniejsza przypadkową koncentrację obciążenia jeszcze przed wprowadzeniem sztywnych ograniczeń. Sztywne limity pozostają konieczne, gdy sama sprawiedliwość nie jest w stanie ograniczyć szczytów obciążenia przy nierównomiernych opóźnieniach.

    Proxy bez oszukiwania samego siebie

    Sieci domowe zmieniają rozkład identyfikatorów. Nie unieważniają one praw fizyki: jeśli każdy identyfikator nadal uruchamia pięćdziesiąt prób jednocześnie, serwery, które opierają się na zachowaniu a nie na adresie IP, mogą nadal blokować takie próby. Rejestruj dane o połączeniach według identyfikatora wyjścia. Rotuj identyfikatory w uprzejmy sposób. Przestrzegaj zasad dotyczących botów oraz warunków umowy. Preferuj umiarkowany tempa działania, nawet gdy są włączone proxy, aby błędy nie mogły doprowadzić do szeroko rozprzestrzenionych skutków.

    Proxy serwerów danych są tańsze i łatwiejsze do zidentyfikowania; te przeznaczone dla użytkowników domowych są droższe i budzą problemy etyczne. Wybieraj starannie; nie traktuj „proxy” jako synonimu rozwiązania problemu.

    Wbuduj dwa niezbędne parametry do wspólnych obudów klienta HTTP używanych przez crawlery: maxInFlight i maxStartsPerSecond, a także maxInFlightPerHost, gdy występuje więcej niż jeden host. Odrzuć możliwość tworzenia klienta w trybie wielu hostów bez ograniczeń na poziomie poszczególnych hostów. Udostępnij szablony paneli kontrolnych pokazujące liczbę udanych zapytań na sekundę. Nauczaj poprzez pokazanie dwóch wykresów: jednego przedstawiającego wysoką przepustowość przy małej liczbie jednoczesnych zapytań, a drugiego pokazującego utratę użytecznych stron.

    • Ustal ograniczenia dla liczby rozpoczynanych zapytań, a nie tylko dla tych w trakcie realizacji.
    • Ustal ograniczenia na poziomie poszczególnych hostów w ramach globalnego puli.
    • Mierz liczbę przyjmowanych zapytań oraz maksymalne wartości na poziomie każdego hosta przy każdym uruchomieniu.
  • Oceniaj sukces pod kątem użytecznych stron, a nie liczby ukończonych połączeń.
  • Zastosuj kontrolę dostępu do prób ponownych.
  • W testach mieszanych zachowaj łagodniejszą kontrolę nad hostem.
  • Traktuj sukces proxy jako zmianę reputacji, a nie dowód na prawidłowe planowanie.
  • Przeglądaj liczby w miarę zmian polityki docelowej; zachowaj metodologię.
  • Dopóki te nawyki się nie utrwalą, zespoły będą nadal „naprawiać problem współbieżności”, co prowadzi do cichszych form awarii, które nadal zwracają błąd HTTP 429 i wciąż zużywają budżet przeglądania.