Od tématu magazínu k projektové praxi
Vhodné stránky služeb a technické stránky k příspěvku
Volání REST je v teorii jednoduché: požadavek ven, odpověď dovnitř, hotovo. V praxi však produktivní integrace málokdy selžou kvůli „špatné URL“, ale kvůli okrajovým případům v provozu: sporadické time-outy, dočasné problémy s DNS nebo TLS, přetížené downstream systémy, nebo 429 (Too Many Requests), protože API‑brána dělá throttling. Právě tady se od sebe odlišuje demo‑prototyp a integrace vhodná pro trvalý provoz.
Tento článek ukazuje, jak s RESTClientem v Delphi zavést robustní komunikační cesty: jasné definice timeoutů, cílené retry pouze tam, kde jsou věcně a technicky bezpečné, a backoff chování, které respektuje rate‑limity místo aby je zhoršovalo. Důraz není na „hezky napsaný kód“, ale na chování pod zatížením, schopnost debugování, čistou klasifikaci chyb a otázku, kdy se dodatečná práce skutečně vyplatí.
Proč se v reálných prostředích timeouty, retry a 429 vyskytují současně
V podnikových sítích volání REST málokdy „jdou přímo na internet“. Typické jsou řetězce proxy, TLS terminace, API‑brány, WAFy (Web Application Firewall) a několik interních hopů. Každé z těchto článků může mít vlastní timeouty a limity. Timeout na straně klienta může znamenat:
- Server neodpověděl (přetížení, deadlock, downstream se zasekl).
- Odpověď přišla, ale příliš pozdě (špatná cesta, ztráta paketů, přetížení sítě / congestion).
- Vyloučil ses sám: příliš krátké timeouty nebo blokující UI-/hlavní vlákno.
Současně „naivní“ retry často vedou k dalším problémům: pokud je server již na svém limitu, retry zvyšují zátěž a z malého úzkého hrdla udělají poruchu. U 429 je to ještě zřetelnější: rate‑limit je explicitní výzva posílat méně nebo se vrátit později. Klient bez backoffu se chová jako generátor DoS, jen neúmyslně.
Robustnost proto nevzniká „retry všude“, ale konzistentním rozhodovacím modelem: které chyby jsou transientní (přechodné), které jsou trvalé, které požadavky jsou retryovatelné (idempotentní) a jak řídíš čekací doby tak, aby systém zůstal stabilní.
Nastavení timeoutů: Co přesně znamená „timeout“ u RESTClienta v Delphi?
Častá past: „timeout“ není totéž co timeout. Podle stacku existují různé fáze. I když komponenty Delphi-REST mnoho věcí zapouzdřují, měl bys mít tento model v hlavě:
- Connect-Timeout: doba do navázání TCP spojení (včetně DNS/TLS v závislosti na implementaci).
- Read/Response-Timeout: doba, než přijdou byty od serveru nebo je odpověď kompletní.
- Celkový timeout: horní hranice pro celé volání včetně retry.
V praxi je příliš krátký timeout minimálně stejně nebezpečný jako příliš dlouhý: vytváříš umělé chyby, které se pak retryují a tím generují zátěž. Naopak příliš dlouhý timeout blokuje pracovní vlákna, místa ve frontě nebo odezvu UI. Pro provoz a administraci je důležité, aby timeouty byly konfigurovatelné (např. pro každý endpoint) a aby byly zapisovány do logu.
Doporučení z praxe: dvě úrovně místo jedné hodnoty
Pro volání REST v podnikové softwaru se osvědčily dvě úrovně:
- Call-Timeout (pro Request): realistická horní hranice, která odpovídá případu použití.
- Job-Timeout (nadřazeně): když máte dávkové zpracování nebo synchronizační úlohu, omezte celkový čas běhu a korektně ukončete.
Tím zabráníte, aby jediná odpověď API čekala věčně, a zároveň aby noční úloha kvůli mnoha opakovaným pokusům „běžela až do poledne“.
Rozhodování o opakování: ne technicky, ale z hlediska doménové logiky
Zdali je opakování povoleno, není čistě technická otázka. Klíčový pojem je idempotence: Požadavek je idempotentní, pokud jeho opakované provedení má stejný efekt jako jednorázové provedení. Typické příklady: GET je idempotentní, PUT často také (pokud cílový objekt nastavíte kompletně), DELETE obvykle také. POST většinou není idempotentní (např. vytvoření nové objednávky).
Proč je to rozhodující? Timeout může znamenat, že server požadavek zpracoval, ale odpověď se k klientovi nedostala. Pokud pak bez rozmyslu zopakujete POST, vytvoříte duplikáty. To je v provozu klasická „duchová chyba“: v aplikaci stojí „Timeout“, v backendu jsou duplicitní záznamy.
Bezpečný základ: opakovat pouze jasně opakovatelné operace
Robustní pravidlo, které se osvědčilo v integracích:
- GET: opakovatelný při přechodných chybách.
- PUT/DELETE: opakovatelný, pokud vaše API věcně jasno definuje chování (např. ID zdroje je stabilní) a server správně implementuje idempotenci.
- POST: pouze opakovatelný, pokud máte strategii Idempotency-Key (věcně jedinečné ID požadavku, které na serveru zabrání duplikátům) nebo pokud je POST sémanticky idempotentní (vzácné, ale možné).
Pokud API nekontrolujete, je to místo, kde jako technický lead musíte rozhodnout: buď akceptujete „žádné opakování u POST“ (a místo toho postavíte lepší chybové zprávy / resynchronizační mechanismy), nebo vyjednáte s poskytovatelem API Idempotency-Key nebo model umožňující deduplikaci.
429 Too Many Requests: respektovat rate-limits místo „slepého opakování“
HTTP 429 není „otravná chybová zpráva“, ale řídicí mechanismus. V podnikovém prostředí přichází 429 často od:
- API-Gateway s Token-Bucket/Leaky-Bucket-limity (Rate Limiting).
- Cloud-APIs s limity pro nájemce za minutu/hodinu.
- Interních služeb, které se chrání před špičkami zátěže.
Pro klienta to znamená: opakování ano, ale kontrolované. Důležité jsou dvě věci:
- Vyhodnotit hlavičku Retry-After, pokud je přítomna (sekundy nebo HTTP-datum).
- Používat backoff, pokud nepřijde Retry-After, nebo pokud navíc přidáte jitter.
Nejčastější úskalí: 429 je zpracován jako 500 („chyba serveru, opakujte ihned“). Tím posílíte omezení. Lepší je: 429 je signál, aktivně počkat a případně snížit paralelitu.
Backoff s Jitterem: proč bez náhody vše synchronně zkolabuje
Exponential Backoff znamená, že po každém neúspěšném pokusu zvýšíte dobu čekání (např. 200 ms, 400 ms, 800 ms …). Jitter je náhodná složka, která zabrání tomu, aby mnoho klientů současně opětovně klepalo na službu. Bez Jitteru se v praxi často stane toto: aktivuje se limit, 50 klientů dostane 429, všichni čekají přesně 1 sekundu a pak znovu odešlou požadavky zároveň. Výsledek: opět 429, máte „Thundering Herd“ problém.
Praktický přístup je „Full Jitter“ nebo „Equal Jitter“: spočítáte backoffové okno a pak vyberete náhodnou dobu čekání v rámci tohoto okna. Zní to jako detail, ale v provozu rozhoduje o tom, zda se systém stabilně zotaví, nebo bude trvale vibrovat.
Čistý vzor: zapouzdřit volání REST, místo rozmístění Retry-smyček všude
Pokud implementujete Retries/Backoff „ad hoc“ na každém místě volání, rychle vznikne nekonzistentní chování: jeden endpoint se opakuje agresivně, jiný vůbec, logování je neúplné a administrátoři vidí jen „sporadické chyby“. Robustní to bude tehdy, když definujete centrální cestu volání:
- Obal (wrapper) kolem RESTClient/RESTRequest, který aplikuje Policy (Timeout, Retry, Backoff).
- Jednotný výsledkový objekt: statusový kód, doba trvání, počítadlo pokusů, případně poslední výjimka.
- Standardizované Logging (Request-ID/Correlation-ID, endpoint, HTTP-metoda, relevantní hlavičky).
Právě zde se dodatečný kód skutečně vyplatí: získáte reprodukovatelné chování, lepší logy a můžete konfigurovat policies pro jednotlivá cílová systémy bez přestavby aplikace.
Policy-rozhodovací matice (krátce a prakticky)
Pro většinu integrací stačí jednoduchá matice, kterou zobrazíte ve wrapperu:
- Retry při: síťové chyby/přerušení spojení, 408, 429, 502, 503, 504 (v závislosti na smluvních podmínkách API).
- Neopakovat při: 400/401/403/404 (obvykle chyba konfigurace/autentizace/requestu), 409/422 (aplikace/validace konflikt), a také u POST bez Idempotency-Key.
- Max. pokusů: držet nízko (často stačí 2–4 pokusy), místo toho lepší monitoring.
- Max. Backoff: omezit (např. několik sekund až minuta), jinak blokujete příliš mnoho workerů.
Důležité: tato pravidla nejsou univerzální. 404 může při „eventual consistency“ být přechodná, 409 může být přechodný při zamykacích strategiích. Rozdíl je v tom, že pak jde o vědomé odchýlení, nikoli náhodné chování.
Konkrétní okrajový případ: Timeout po POST – bylo to uloženo, nebo ne?
To je klasika, kterou se v debuggeru málokdy podaří čistě reprodukovat: odešleš POST (např. „Ticket anlegen“), tvůj klient dostane Read-Timeout a uživatel klikne „nochmal“. Na backendu ale ticket už existuje. Bez protiopatření vznikají duplikáty nebo nekonzistence.
Robustní to bude pouze s jednou ze tří strategií:
- Idempotency-Key: Vytvoříš pro každý věcný proces jednoznačné Request-ID (např. GUID), pošleš ho v headeru a server garantuje deduplikované zpracování.
- Deduplikace na straně klienta: Ukládáš lokálně „pending requests“ s vlastním ID a po timeoutu provedeš kontrolu stavu (např. GET podle věcného klíče). To je náročnější a ne vždy proveditelné.
- Bez opakování: Jasně oznámíš, že stav je neznámý, a zřídíš manuální/automatický resync-proces (např. pozdější porovnání).
Pokud buduješ integrace pro provoz, je „Status unbekannt“ platnou kategorií. Nesnaž se nejistotu zakódovat pryč. Loguj ji, zobraz ji a zajisti synchronizační cestu.
Backoff-Design in der Praxis: Grenzwerte, Parallelität und Cancel
Backoff není jen „Sleep“. Musíš ho zasadit do kontextu své aplikace:
- Paralelita: Pokud máš 20 vláken a všechna čekají, je 20 vláken zablokovaných. U služeb je to často v pořádku, u desktopových aplikací spíš ne.
- Zrušení: Uživatel přeruší, služba se zastaví, job se ukončí. Čekání v backoffu musí být zrušitelné, jinak se procesy zastavení/ukončení zaseknou.
- Férovost: Více endpointů by se nemělo navzájem „vyhladovět“. Rate-Limits jsou často na token nebo na endpoint; tvůj wrapper by měl umět řídit limity podle cílového systému.
Čistý přístup je: backoff v rámci funkce, která čeká v malých intervalech a přitom kontroluje cancel-flag (např. Event/Token). To není luxus: právě toto místo rozhoduje, zda se Windows- a Linux-Services korektně zastaví nebo v konzoli Service Control Manager „visí“.
Maximální doba a „rozpočet“ na volání
Robustní implementace retry nepracuje jen s „max tries“, ale také s časovým rozpočtem. Příklad: povolíš maximálně 10 sekund celkového času pro volání včetně opakování. Pak jediný pokus nemůže náhle blokovat 30 sekund jen proto, že byl špatně nastaven timeout. Pro administrátory a provoz to má velkou hodnotu, protože omezuje špičky latence a stabilizuje fronty.
Debugging und Betriebsdiagnose: Ohne gute Logs sind Retries unsichtbare Fehlerverstärker
Opakovaná volání bez logování jsou nebezpečná, protože nakonec slyšíš jen „někdy to trvá“. Pokud chceš být odolný, potřebuješ logy, které nevypisují jen výjimky, ale poskytují kontext:
- Correlation-ID: ID požadavku, kterou vytvoříš pro každý volání a zachováš ji při každém opakování.
- Číslo pokusu a prodleva (backoff).
- HTTP-Status a vybrané hlavičky (zejména Retry-After, RateLimit-Header, pokud jsou k dispozici).
- Trvání pro každý pokus a celková doba.
- Endpoint (host + cesta), ale žádná citlivá data v logu (tokeny, osobní údaje).
Pro technické leady je to navíc páka pro doladění mezních hodnot: Vidíš, jestli time-outy „vždy po 3 sekundách“ nastávají (pravděpodobně příliš krátké) nebo jestli 429 přichází v vlnách (příliš vysoká paralelita, slabý backoff nebo chybějící klientská omezení rychlosti).
Typické pasti v logování
- Příliš velký payload: Kompletní logování JSON těla se zdá užitečné, ale u souborů/příloh exploduje a způsobuje problémy s ochranou osobních údajů. Lepší: hash/velikost, Content-Type a v případě potřeby cílené debug-logování přes feature-flag.
- Žádné rozlišení Timeout vs. Cancel: Přerušené volání není chybou ve stejném smyslu jako timeout. Odděl to, jinak budou administrátoři honit fantomové chyby.
- Opakování zakryje původní příčinu: Pokud první pokus měl chybu TLS a druhý pokus je úspěšný, i tak chceš vědět, že došlo k nestabilitě TLS. To je varovný signál.
Omezení rychlosti na klientské straně: Když musíš sám řídit zátěž
429 je odpověď serveru. V mnoha scénářích je ale rozumné už na straně klienta omezit provoz dříve, než začneš produkovat 429. To platí zejména, pokud:
- máš dávkové úlohy (např. noční synchronizace dat) a API povolí jen X požadavků za minutu.
- používáš více workerů/threadů a posíláš požadavky paralelně.
- běží více instancí procesu (např. terminálový server nebo více služeb).
Prakticky to znamená: implementuješ malý rate-limiter (např. token-bucket) pro cílový systém nebo pro každý API-key. To redukuje 429, stabilizuje průtok a dělá doby běhu lépe plánovatelnými. Pro provoz a kapacitní plánování je to často cennější než „ještě jedno opakování“.
Důležité: Rate-Limiter a backoff se doplňují
Rate-limiter tě udrží v normálním provozu pod limitem. Backoff je reakce, když přesto dostaneš 429 nebo dočasné přetížení. Ten, kdo má jen backoff, pořád „naráží do zdi“ a pak brzdit. Ten, kdo má jen rate-limiter, špatně reaguje na překvapivá omezení nebo sdílené kvóty (např. když více systémů používá tentýž API-key).
Bezpečnost a compliance: Opakování nesmí zakrývat autentizační problémy
Ve firmách jsou autentizace a autorizace často nejčastější „chybou“ po nasazení: expirované tokeny, špatně nakonfigurované klientské přihlašovací údaje, chybějící výjimky pro proxy. Opakování v tomhle nepomůže a může být dokonce škodlivé, protože zaplní logy a spustí blokovací mechanizmy (např. zablokování účtu, limity na autentizačních endpointech).
Praktické pravidlo: 401/403 nikdy neopakovat (pokud nemáš záměrné řešení obnovy tokenu). Pokud implementuješ obnovu tokenu, odděl to jasně od retry-mechanismu: nejdřív obnov token, pak jednou pošli znovu. A explicitně zaloguj, že k obnově došlo.
Kdy se námaha vyplatí – a kdy ne
Robustní opakování pokusů (Retries) a backoff nejsou cílem samy o sobě. Vyplatí se zejména, pokud platí alespoň jeden z následujících bodů:
- Integrace je obchodně kritická (např. zadávání objednávek, expedice, fakturace).
- API je externí nebo interně provozovaná pouze „best effort“ a nemáš plnou kontrolu.
- Máte špičky zátěže (např. okna úloh, měsíční uzávěrky) a chcete projít stabilně.
- Provozujete to jako službu/daemona a musíte být schopni jej plánovatelně a čistě zastavovat.
Méně se to vyplatí, pokud máš výhradně „potvrzující GETy“ v UI a uživatel stejně znovu klikne, nebo pokud pracuješ v interním, velmi stabilním prostředí bez kvót a chyby jsou okamžitě viditelné. I v těchto případech jsou ale dobře nastavené timeouty a logování téměř vždy rozumné.
Pragmatický checklist pro produktivní provoz Delphi-RESTClientu
- Timeouts: konfigurovatelné na každý endpoint, realisticky nastavené, definovaný celkový rozpočet.
- Retry-Policy: závislá na HTTP metodě a idempotenci, nikoli paušální.
- 429-Handling: vyhodnotit Retry-After, backoff s jitterem, mít na paměti paralelitu.
- Abbruchpfad: čekání během backoffu přerušitelné (zastavení služby, zrušení uživatelem).
- Logging: Correlation-ID, pokus (Attempt), zpoždění (Delay), doba trvání, stav/hlavičky – bez tajných údajů.
- Optional: na klientské straně rate-limiter pro dávkový/paralelní provoz.
Fazit: Robustnost je chování, ne „catch-all“ Exception-Block
S RESTClientem v Delphi rychle dosáhneš fungujících REST volání. Do produkční robustness se ale dostaneš až tehdy, když vědomě definuješ timeouty, opakování pokusů odborně zajistíš (idempotence!), a 429 rate-limity budeš respektovat s backoffem a jitterem. Kód pro to není složitý, ale musí být centralizovaný, konfigurovatelný a dobře pozorovatelný. Tehdy se námaha vyplatí: méně „sporadických“ tiketů, lepší diagnostika v provozu a integrace, které i pod zátěží neztratí stabilitu.
Pokud chceš takovou retry-/backoff politiku u stávajících Delphi aplikací čistě zavést nebo pro novou integraci vhodně dimenzovat: Kontakt aufnehmen.
Pro toto téma jsou také důležité Delphi Restclient Timeout a Retry-Strategie Delphi. Článek tyto aspekty srozumitelně zařadí a ukáže, na co jde v každodenní praxi.
další krok
Když se z tématu stane reálný projekt, měly by být architektura, stávající systém a provoz posuzovány společně již v rané fázi.
Podporujeme nejen při jednotlivých otázkách, ale i v případě, že se z útržků zdrojového kódu, legacy témat nebo nápadů na portál má vyvinout robustní podnikový projekt.
- Současný stav, cílový stav a technická rizika jsou hodnoceny společně.
- REST, přístup k datům, portály a rollout nebudou přesunuty do pozdějších fází.
- Včas zjistíte, která varianta je ekonomicky i provozně životaschopná.