Spring til indhold

Latenstid

ventetiden mellem spørgsmålet og svarets begyndelse

Hvad det er

Latenstid, ofte bare latens, er den tid, et system er om at begynde at svare: fra en forespørgsel forlader klienten, til den første byte af svaret er tilbage. Man taler også om forsinkelse, om rundturstid (round-trip time, RTT), når en netværksstrækning måles frem og tilbage, og i webperformance om time to first byte (TTFB). Svartid er den større storebror, der løber helt til svarets sidste byte.

Latenstid forveksles let med gennemløb og båndbredde, men de tre besvarer hvert sit spørgsmål. Båndbredde er, hvor mange bit en forbindelse kan bære i sekundet. Gennemløb er, hvor meget arbejde et system gør færdigt i sekundet. Latenstid er, hvor længe ét stykke arbejde venter. Et natligt batchjob kan have glimrende gennemløb og elendig latenstid, og en chatserver omvendt.

Hvert millisekund stammer fra et af få steder:

  • udbredelse, signalets vej over afstanden: lys i optisk fiber tilbagelægger omkring 200 km pr. millisekund, så en strækning på 1.000 km koster mindst 10 ms frem og tilbage, før noget software overhovedet kører;
  • transmission, at få bytes ud på ledningen, hvilket betyder noget for store datamængder på langsomme forbindelser;
  • kø, ventetid bag andet arbejde i en routerbuffer, en trådpulje, en forbindelsespulje eller en lås, og den eneste del, der vokser med belastningen;
  • behandling, selve arbejdet: forespørgslen, skabelonen, krypteringen;
  • protokolrundture, samtalen før den første nyttige byte. En ny HTTPS-forbindelse kræver først et TCP-håndtryk og et TLS-håndtryk, og TLS 1.3 skar det fulde håndtryk ned til én rundtur.

IETF’s arbejdsgruppe for IP Performance Metrics definerede netværksudgaven præcist i RFC 2681: rundturstiden løber fra den første bit af en pakke sendes, til den sidste bit af svaret er modtaget. To af valgene i den gælder for enhver tjeneste. Et svar, der aldrig kommer, tælles som udefineret, uformelt uendeligt, i stedet for at forsvinde stille ud af stikprøven. Og de statistikker, RFC’en beskriver, er percentiler, medianen og minimum. Den bemærker også, at minimum afspejler udbredelse og transmission alene, mens alt over minimum skyldes trængsel, en opdeling der holder lige så godt for et API som for en vej mellem routere.

For en enkelt webforespørgsel kan curl dele ventetiden op i faser:

curl -s -o /dev/null "$URL" -w '
dns        %{time_namelookup}
connect    %{time_connect}
tls        %{time_appconnect}
first byte %{time_starttransfer}
total      %{time_total}
'

Hvert tal er sekunder siden start, så afstanden mellem dem er prisen for hvert trin: navneopslaget, forbindelsen, TLS-håndtrykket og til sidst serverens egen betænkningstid frem til den første byte.

Hvorfor det betyder noget

Latenstid er det, brugeren mærker; gennemløb er det, serverregningen afspejler. Når en side er langsom, er beslutningen, hvor pengene skal bruges, og de fire årsager ovenfor har fire forskellige løsninger, der ikke kan erstatte hinanden. Afstand løses ved at flytte tættere på: en cache, et CDN, en region nær brugerne. Rundture løses ved at få færre af dem: genbrugte forbindelser, samlede forespørgsler, parallelle kald. Behandling løses ved at lave mindre arbejde: et indeks, en bedre forespørgselsplan. Kun kø løses med kapacitet. En forespørgsel, der tager to sekunder på en ledig server, tager også to sekunder på en server, der er ti gange større, og derfor er mere hardware kun svaret på én af de fire.

Gennemsnit får problemet til at se mindre ud, end det er, for brugerne møder halen. En side, der kalder ti backend-tjenester parallelt, venter på den langsomste. Er hvert kald langsomt 1 % af tiden, rammer 1 − 0,99¹⁰, omkring 9,6 % af sidevisningerne, mindst ét langsomt kald. En sjælden forsinkelse i delene bliver en almindelig forsinkelse i helheden.

Derfor skrives seriøse mål i percentiler, for eksempel at 99 % af forespørgslerne besvares inden for 300 ms over 30 dage, og derfor er den 99. percentil som regel det tal, der er værd at holde øje med. Medianen fortæller, hvordan tjenesten føles på en god dag; de høje percentiler fortæller, hvordan den føles for de kunder, der er på vej væk.

Sådan ser det ud i praksis

I en geografisk kortapplikation gik latens ved rendering af kortlag og forespørgsler på lokationsdata ud over både brugerne og backendens pålidelighed. Databasen nedenunder, Microsoft SQL Server, manglede native geospatial indeksering, så den langsomme del var behandling og ikke afstand. Skiftet til PostgreSQL med PostGIS gav en gennemsnitlig svartid på under 250 millisekunder, ned fra 2,5 sekunder, og dermed en tidobling af ydeevnen for spatiale forespørgsler. Arbejdet bag var GiST- og SP-GiST-indeks på de spatiale kolonner, over 50 millioner records migreret og en konfiguration tunet til samtidig adgang; backendens CPU-belastning faldt med 65 %.

Latenstid sætter også loft over gennemløbet, hvor én række er omstridt. En note om flash-salg, hvor alle købere skriver til den samme række, sætter loftet for en enkelt vare til omtrent de salg, én lås bærer, divideret med den tid, låsen holdes. Alt langsomt inde i låsen sænker loftet: et betalingskald, mens låsen holdes, bringer det ned på omkring to salg i sekundet. Selv en sikkerhedsforanstaltning har en pris her. En synkron standby i PostgreSQL lægger latens på hver commit, og det forlænger præcis den holdetid, loftet divideres med.

Og for at finde ud af, hvor tiden går, registrerer den hjemmebyggede overvågning på én platform API-latenstid i platformens egen database og sporer forespørgsler over OpenTelemetry, så en langsom forespørgsel kan følges gennem frontend, API og database i stedet for at blive gættet på.

Hvor det går galt

Gennemsnittet skjuler halen. Et gennemsnit på 100 ms er foreneligt med, at hver halvtredsindstyvende forespørgsel tager tre sekunder. Registrér latenstid som et histogram, og læs percentilerne af det. Pas så på, hvordan de lægges sammen: den 99. percentil fra fem servere kan ikke gennemsnitsberegnes til hele flådens 99. percentil. Prometheus siger det selv i sin dokumentation: kvantiler beregnet af en summary kan ikke aggregeres, mens histogrammets spande kan lægges sammen først, hvorefter kvantilen beregnes.

Uret starter det forkerte sted. En server, der måler fra det øjeblik, dens handler begynder, overser den tid, forespørgslen brugte i load balanceren, i accept-køen eller på at vente på en forbindelse fra puljen. Belastningsværktøjer, der venter på hvert svar, før de sender den næste forespørgsel, sender færre forespørgsler, netop når systemet går i stå, så stoppet næsten ikke ses i resultaterne. Mål også fra klienten, og lad serveren melde sin andel i en standardheader:

Server-Timing: db;dur=53, app;dur=47.2

Samtalen er for snakkesalig. En side, der kører én forespørgsel pr. række i en liste, betaler én rundtur pr. række. Står databasen i en anden region med 30 ms pr. rundtur, koster hundrede rækker tre sekunder, før nogen forespørgsel laver rigtigt arbejde. Én forespørgsel med en join, eller ét samlet kald, betaler afstanden én gang.

Genforsøg ganger belastningen op. En klient, der giver op efter et sekund og prøver igen tre gange, gør én langsom server til fire gange trafikken, hvilket gør den endnu langsommere. Giv hver forespørgsel en samlet tidsfrist, vent eksponentielt længere med tilfældig spredning mellem forsøgene, og sæt et loft over antallet af genforsøg.

Læs mere

Ordliste