Net-Base Magasin

08.08.2026

RESTClient i Delphi: Robust over for timeouts, retries og 429-ratebegrænsninger med backoff

Når REST-opkald i Delphi sporadisk hænger, udløser timeouts eller returnerer 429 Rate-Limits, er 'bare send igen' ikke nok. Denne praksisartikel viser, hvordan du med RESTClient håndterer kontrollerede timeouts, sikre retries, backoff med jitter og konsekvent logging...

08.08.2026

Fra magasinets tema til projektpraksis

Passende service- og tekniske sider til artiklen

Et REST-opkald er i teorien enkelt: Request ud, Response ind, færdig. I praksis fejler produktive integrationer dog sjældent på „forkert URL“, men på driftsmæssige kanttilfælde: sporadiske timeouts, kortvarige DNS- eller TLS-problemer, overbelastede downstream-systemer eller 429 (Too Many Requests), fordi et API-Gateway begrænser. Netop her adskiller en demo-prototype sig fra en permanent driftbar integration.

Dette indlæg viser, hvordan du med RESTClient i Delphi etablerer robuste kommunikationsveje: klare timeout-definitioner, målrettede Retries kun dér, hvor de fagligt og teknisk er sikre, og en Backoff-adfærd, der respekterer Rate-Limits i stedet for at forværre dem. Fokus ligger ikke på „pæn kode“, men på adfærd under belastning, fejlsøgningsmuligheder, korrekt fejlklassifikation og spørgsmålet om, hvornår den ekstra indsats faktisk er berettiget.

Hvorfor Timeouts, Retries og 429 optræder sammen i produktionsmiljøer

I virksomhedsnetværk løber REST-Calls sjældent „direkte ud på internettet“. Typisk ser man proxy-kæder, TLS-terminering, API-Gateways, WAFs (Web Application Firewall) og flere interne hops. Hvert led kan have egne Timeouts og Limits. Et Timeout på klientsiden kan betyde:

  • Serveren har ikke svaret (overbelastning, Deadlock, downstream hænger).
  • Svaret kom, men for sent (dårlig rute, pakketab, Congestion).
  • Du har låst dig selv ude: for korte Timeouts eller en blokerende UI-/Main-Thread.

Samtidig fører „naive“ Retries ofte til flere problemer: Hvis en server allerede er ved sit limit, øger Retries belastningen og forvandler en lille flaskehals til en større forstyrrelse. Ved 429 er det endnu tydeligere: Et Rate-Limit er en eksplicit opfordring til at sende mindre eller komme tilbage senere. En klient uden Backoff opfører sig som en DoS-generator, blot utilsigtet.

Robusthed opstår derfor ikke ved „Retry overalt“, men gennem et konsistent beslutningsmodel: Hvilke fejl er transient (midlertidige), hvilke er permanente, hvilke Requests er retrybare (idempotente), og hvordan styrer du ventetiderne, så dit system forbliver stabilt.

Timeouts korrekt sætte: Hvad præcist betyder „Timeout“ for RESTClient i Delphi?

En hyppig faldgrube: „Timeout“ er ikke nødvendigvis det samme som Timeout. Afhængigt af stacken er der forskellige faser. Selvom Delphi-REST-komponenter kapsler meget, bør du have modellen i hovedet:

  • Connect-Timeout: Tid indtil TCP-forbindelsen er etableret (inklusive DNS/TLS afhængig af implementering).
  • Read/Response-Timeout: Tid indtil bytes kommer fra serveren, eller svaret er fuldstændigt.
  • Gesamt-Timeout: Øvre grænse for det samlede kald inklusive Retries.

I praksis er en for kort Timeout mindst lige så farlig som en for lang: Du skaber kunstige fejl, som så bliver retried og dermed genererer belastning. Omvendt blokerer en for lang Timeout worker-threads, queue-pladser eller UI-reaktivitet. For drift og administration er det vigtigt, at Timeouts er konfigurerbare (f.eks. pr. Endpoint) og at de bliver skrevet i loggen.

Anbefaling fra praksis: To niveauer i stedet for ét tal

For REST-Calls i Business-Software har to niveauer vist sig effektive:

  • Call-Timeout (pr. Request): realistisk øvre grænse, der passer til use-casen.
  • Job-timeout (overordnet): hvis du har en batchbehandling eller et sync-job, begræns den samlede køretid og afbryd ordentligt.

Så undgår du, at et enkelt API-svar venter for evigt, og samtidig, at et natligt job på grund af mange gentagne forsøg „kører indtil middag“.

Genforsøg: beslut ikke teknisk, men fagligt

Om et genforsøg er tilladt, er ikke kun et teknisk spørgsmål. Kernebegrebet er Idempotens: En request er idempotent, hvis den kørt flere gange giver samme effekt som én enkelt kørsel. Typiske eksempler: GET er idempotent, PUT ofte også (hvis du sætter målobektet fuldstændigt), DELETE som regel også. POST er ofte ikke idempotent (f.eks. “opret ny ordre”).

Hvorfor er det afgørende? Et timeout kan betyde, at serveren alligevel har behandlet requesten, men at svaret ikke nåede klienten. Hvis du så blindt gentager en POST, skaber du duplikater. Det er i drift en klassisk „spøgelsesfejl“: I applikationen står der „Timeout“, i backend findes dublerede poster.

Den sikre basis: Genforsøg kun for klart genforsøgbare operationer

En robust regel, der har vist sig i integrationer:

  • GET: genforsøgbart ved transiente fejl.
  • PUT/DELETE: genforsøgbart, hvis din API har det fagligt klart defineret (f.eks. at ressource-ID er stabilt) og serveren korrekt implementerer idempotens.
  • POST: kun genforsøgbart, hvis du har en Idempotency-Key-strategi (fagligt entydig request-ID, der server-side forhindrer duplikater) eller hvis POSTen semantisk er idempotent (sjældent, men muligt).

Hvis du ikke kontrollerer APIen, er det her, du som teknisk lead må træffe en beslutning: Enten accepterer du „intet genforsøg ved POST“ (og bygger bedre fejlbeskeder/resync-mekanismer), eller også forhandler du med API-udbyderen om en Idempotency-Key eller et deduplikerbart modelmønster.

429 Too Many Requests: respekter rate-limits i stedet for at genforsøge ukontrolleret

Grafik af et API-gateway med begrænsede requests og backoff-intervaller
Ved 429 hjælper kontrolleret backoff: færre samtidige gentagelser, mere stabil genopretning.

HTTP 429 er ikke en „irriterende fejlmeddelelse“, men en styringsmekanisme. I virksomheder ser man 429 ofte fra:

  • API-gateway med Token-Bucket/Leaky-Bucket-begrænsninger (Rate Limiting).
  • Cloud-APIs med begrænsninger per lejer pr. minut/time.
  • Interne services, der beskytter sig mod belastningstoppe.

For klienten betyder det: Genforsøg ja, men kontrolleret. To ting er vigtige:

  • Vurdér Retry-After-headeren, hvis den er til stede (sekunder eller HTTP-dato).
  • Brug backoff, hvis der ikke kommer en Retry-After, eller kombinér med jitter.

Den hyppigste faldgrube: 429 behandles som 500 („Serverfejl, genforsøg med det samme“). Det forstærker throttlingen. Bedre er: 429 er et signal om at vente aktivt og eventuelt reducere paralleliteten.

Backoff med Jitter: hvorfor uden tilfældighed kollapser alt synkront

Eksponentiel Backoff betyder, at du øger ventetiden efter hvert fejlforsøg (f.eks. 200 ms, 400 ms, 800 ms …). Jitter er en tilfældig komponent, der forhindrer, at mange klienter banker på samtidig. Uden Jitter sker der i praksis ofte følgende: En grænse træder i kraft, 50 klienter får 429, alle venter præcis 1 sekund og sender så igen samtidigt. Resultat: igen 429, og du har et „Thundering Herd“-problem.

En praktisk tilgang er „Full Jitter“ eller „Equal Jitter“: Du beregner et backoff-vindue og vælger derefter en tilfældig ventetid inden for dette vindue. Det lyder som en detalje, men i drift gør det forskellen mellem stabil genopretning og vedvarende gentagne fejl.

Et rent mønster: REST-kald indkapsle, i stedet for at sprede Retry-løkker overalt

Hvis du implementerer Retries/Backoff „ad hoc“ ved hver callsite, opstår hurtigt inkonsistent adfærd: én Endpoint retrier aggressivt, en anden slet ikke, Logging er mangelfuld, og administratorerne ser kun „sporadiske Fehler“. Robust bliver det, når du definerer en central opkaldsvej:

  • En wrapper omkring RESTClient/RESTRequest, der anvender Policy (Timeout, Retry, Backoff).
  • Et ensartet Ergebnisobjekt: Statuscode, varighed, forsøgstæller, evt. sidste Exception.
  • Standardiseret Logging (Request-ID/Correlation-ID, Endpoint, HTTP-Methode, relevante Header).

Det er punktet, hvor ekstra kode virkelig betaler sig: Du får reproducerbar adfærd, bedre Logs, og du kan konfigurere Policies pr. målsystem uden at ændre applikationen.

Policy-beslutningsmatrix (kort og praktisk)

For de fleste integrationer er en enkel matrix tilstrækkelig, som du afbilder i wrapperen:

  • Retry ved: netværksfejl/forbindelsesafbrydelser, 408, 429, 502, 503, 504 (afhængigt af API-aftalen).
  • Ingen Retry ved: 400/401/403/404 (typisk konfigurations-/autentifikations-/Request-fejl), 409/422 (faglige konflikter/validering), samt ved POST uden Idempotency-Key.
  • Max. Versuche: hold dem lave (ofte er 2–4 forsøg nok), til gengæld bedre Monitoring.
  • Max. Backoff: begræns (f.eks. få sekunder til et minut), ellers blokerer du for for mange Worker.

Vigtigt: Disse regler er ikke universelle. 404 kan ved „eventual consistency“ godt være transient, 409 kan ved Locking-Strategien være transient. Forskellen er: Så er det en bevidst afvigelse, ikke tilfældig adfærd.

Konkrett randtilfælde: Timeout efter POST – blev det nu gemt eller ej?

Papierdiagramme und Notizen, die einen unklaren POST-Status nach Timeout visualisieren
Timeout efter POST er farligt: uden idempotens forbliver tilstanden fagligt uklar.

Det er klassikeren, som sjældent lader sig reproducere rent i debuggeren: Du sender et POST (f.eks. „Opret ticket“), din klient får et Read-Timeout, og brugeren klikker „igen“. I backend eksisterer ticketen allerede. Uden modforanstaltning opstår duplikater eller inkonsistenser.

Det bliver kun robust med en af tre strategier:

  • Idempotency-Key: Du genererer for hver faglig operation en entydig Request-ID (f.eks. GUID), sender den som header, og serveren garanterer deduplikeret behandling.
  • Klientside-deduplikering: Du gemmer „pending requests“ med egen ID lokalt og foretager efter timeout et status-tjek (f.eks. GET efter faglig nøgle). Det er mere komplekst og ikke altid muligt.
  • Intet Retry: Du rapporterer klart, at status er ukendt, og opbygger en manuel/automatisk resync-proces (f.eks. senere afstemning).

Hvis du bygger integrationer til drift, er „Status unbekannt“ en gyldig kategori. Forsøg ikke at kode usikkerhed væk. Log dem, gør dem synlige, og sørg for en afstemningssti.

Backoff-design i praksis: grænseværdier, parallelitet og afbrydelse

Et Backoff er ikke blot „Sleep“. Du skal sætte det i kontekst af din applikation:

  • Parallelitet: Hvis du har 20 Threads og alle venter, er 20 Threads blokeret. For services er det ofte acceptabelt, for desktop-apps snarere ikke.
  • Cancel: En bruger afbryder, servicen stopper, et job afsluttes. Backoff-ventning skal kunne afbrydes, ellers hænger stop-/Shutdown-processer.
  • Fairness: Flere Endpoints bør ikke sulte hinanden. Rate-Limits er ofte per Token eller per Endpoint; din Wrapper bør kunne styre per målsystem.

En ordentlig tilgang er: Backoff i en funktion, der venter i korte intervaller og undervejs tjekker et Cancel-Flag (f.eks. Event/Token). Det er ikke luksus: Netop dette sted afgør, om en Windows- og Linux-services stopper ordentligt eller i Service Control Manager-konsollen „hænger“.

Maksimal varighed og „Budget“ per kald

En robust Retry-implementering arbejder ikke kun med „max tries“, men også med et Tidbudget. Eksempel: Du tillader maksimalt 10 sekunders samlet tid for kaldet inklusive Retries. Så kan et enkelt forsøg ikke pludselig blokere i 30 sekunder, blot fordi en Timeout er forkert sat. For Admins og drift er det guld værd, fordi det begrænser latenstop og stabiliserer ventekøer.

Debugging og driftsdiagnose: Uden gode Logs er Retries usynlige fejlforstærkere

Arbejdspladsscene med slørede logs og skitseret kontekst for Request-IDs og Retries
Med Correlation-ID, Attempt-tæller og varighed bliver Retries i drift eftersporbare.

Gentagne forsøg uden logging er farlige, fordi du i sidste ende kun hører „det tager nogle gange“. Hvis du vil blive robust, har du brug for logs, der ikke kun udskriver undtagelser, men leverer kontekst:

  • Correlation-ID: en Request-ID, som du genererer per kald og bevarer ved hvert retry.
  • Forsøgsnummer og Forsinkelse (Backoff).
  • HTTP-status og udvalgte headers (især Retry-After, RateLimit-headers hvis tilgængelige).
  • Varighed pr. forsøg og samlet tid.
  • Endepunkt (host + sti), men ingen følsomme data i loggen (tokens, personoplysninger).

For tekniske leads er det desuden håndtaget til at justere grænseværdier: Du ser, om timeouts „altid ved 3 sekunder“ sker (sandsynligvis for korte) eller om 429 kommer i bølger (parallelitet for høj, backoff for svag eller manglende klient-side rate-limits).

Typiske log-fælder

  • For meget payload: At logge komplette JSON-bodies virker hjælpsomt, men eksploderer ved filer/vedhæftninger og skaber databeskyttelsesproblemer. Bedre: hash/størrelse, Content-Type, og ved behov målrettet debug-logging via feature-flag.
  • Ingen skelnen mellem Timeout vs. Cancel: Et afbrudt kald er ikke en fejl i samme forstand som et timeout. Skil dem ad, ellers jagter administratorer spøgelsesfejl.
  • Retry skjuler den første årsag: Hvis forsøg 1 har en TLS-fejl, og forsøg 2 lykkes, vil du alligevel vide, at der var en TLS-ustabilitet. Det er et tidligt varsel.

Klientside-rate-limiting: Når du selv skal styre belastningen

429 er serverens respons. I mange scenarier giver det dog mening at throttle klient-side, før du overhovedet udløser 429. Det er især relevant, hvis du:

  • har batch-jobs (fx data-synk om natten) og API’en kun tillader X requests per minut.
  • bruger flere workers/tråde og affyrer requests parallelt.
  • har flere procesinstanser kørende (fx terminalservere eller flere services).

Praktisk betyder det: Implementer en lille rate-limiter (fx token-bucket) per målssystem eller per API-key. Det reducerer 429, stabiliserer throughput og gør svartider mere forudsigelige. For drift og kapacitetsplanlægning er det ofte mere værd end „endnu et retry“.

Vigtigt: Rate-limiter og Backoff supplerer hinanden

Rate-limiteren holder dig i normaldrift under grænsen. Backoff er reaktionen, når du alligevel får 429 eller midlertidig overbelastning. Den, der kun har backoff, kører konstant „ind i muren“ og bliver derefter bremset. Den, der kun har rate-limiter, reagerer dårligt på overraskende grænser eller delte kvoter (fx når flere systemer bruger samme API-key).

Sikkerhed og compliance: Retries må ikke skjule auth-problemer

I virksomheder er autentificering og autorisation ofte den hyppigste „fejl“ efter deployment: udløbne tokens, forkert konfigurerede client-credentials, manglende proxy-undtagelser. Retries hjælper ikke her og kan endda være skadelige, fordi de fylder logfiler og kan udløse låse- eller spærringsmekanismer (fx account-locks, rate-limits på auth-endpoints).

Praktisk regel: 401/403 aldrig retry (medmindre du har et bevidst Token-Refresh-håndtering). Hvis du implementerer token-refresh, adskil det klart fra retry-mekanismen: Forny token først, send så én gang igen. Og log eksplicit, at der fandt et refresh sted.

Hvornår indsatsen er umagen værd – og hvornår ikke

Robuste retries og backoff er ikke et mål i sig selv. De er især nyttige, når mindst ét af følgende punkter gælder:

  • Integration er forretningskritisk (f.eks. ordreoptagelse, forsendelse, fakturering).
  • API’en er ekstern eller kun drevet internt på „best effort“-basis, og du har ikke fuld kontrol.
  • Du oplever belastningstoppe (f.eks. jobvindue, månedsafslutning) og vil komme stabilt igennem.
  • Du driver det som en service/daemon, og det skal kunne stoppes kontrolleret og forudsigeligt.

Det er mindre relevant, hvis du udelukkende har „Bestätigungs-GETs“ i UI’et og brugeren alligevel klikker igen, eller hvis du arbejder i et internt, meget stabilt miljø uden kvoter, hvor fejl er straks synlige. Selv da er ryddelige timeouts og logging dog næsten altid fornuftigt.

Pragmatisk tjekliste til produktiv Delphi-RESTClient-drift

  • Timeouts: konfigurerbar per Endpoint, realistisk valgt, samlet budget defineret.
  • Retry-Policy: afhængig af HTTP-metode og idempotens, ikke generisk.
  • 429-Handling: læs Retry-After, backoff med jitter, hold øje med parallelitet.
  • Abbruchpfad: backoff-ventetid kan afbrydes (service-stop, brugerannullering).
  • Logging: Correlation-ID, forsøg, forsinkelse, varighed, status/headers – uden hemmeligheder.
  • Optional: klientside rate-limiter til batch-/parallelkørsel.

Fazit: Robusthed ist ein Verhalten, kein Catch-all-Exception-Block

Med RESTClient i Delphi får du hurtigt fungerende REST-kald på plads. Det bliver først produktionsrobust, når du bevidst definerer timeouts, sikrer retries fagligt (Idempotenz!), og respekterer 429 rate-limits med backoff og jitter. Koden til det er ikke kompliceret, men den skal være centraliseret, konfigurerbar og godt observerbar. Netop dér betaler indsatsen sig: færre „sporadiske“ tickets, bedre diagnostik i driften og integrationer, der også under belastning forbliver stabile.

Hvis du vil indføre en sådan retry-/backoff-policy i eksisterende Delphi-applikationer på en ordentlig måde eller dimensionere den passende for en ny integration: Kontakt os.

Til dette emne er også Delphi Restclient Timeout og Retry-Strategie Delphi vigtige. Bidraget sætter disse aspekter i en forståelig sammenhæng og viser, hvad der er vigtigt i hverdagen.

Drøft projekt eller moderniseringsforløb med Net-Base.

Næste trin

Når emnet bliver til et reelt projekt, bør arkitektur, eksisterende systemer og drift tidligt vurderes samlet.

Vi støtter ikke kun ved enkeltspørsmål, men også når kildekodeudsnit, legacy-komponenter eller portalidéer skal udvikles til et robust virksomhedsprojekt.

  • Eksisterende tilstand, målbillede og tekniske risici vurderes samlet.
  • REST, dataadgang, portaler og udrulning bliver ikke udskudt som efterfølgende opgaver.
  • De ser tidligt, hvilken vej der er økonomisk og driftsmæssigt bæredygtig.

Del indlæg

Del dette indlæg direkte

LinkedIn, X, XING, Facebook, WhatsApp og e-mail er straks tilgængelige. Til Instagram forbereder vi link og kort tekst.

E-mail

Instagram åbner i en ny fane. Linket og kortteksten kopieres på forhånd til udklipsholderen.