Distributedpacket lossjitterretransmissiontail latencynetwork

Packet Loss Buys You a Timeout, Not a Retransmit

A link that drops one packet in a thousand looks almost perfect on an average-latency graph. What it actually does is give one request in a few hundred an extra couple of hundred milliseconds, which is invisible at p50 and dominates p99.

Follow the diagnosis

Frame the diagnosis

Performance work starts from a symptom and a signal — never from a resource dashboard.

Diagnostic question
Average latency is unchanged and p99 doubled — could the network be doing this even though bandwidth looks fine?
Symptom
p50 is flat and p99 is spiky and irregular. The slow requests are not correlated with any particular endpoint, tenant, or piece of code, and they do not reproduce in testing.
Signal
TCP retransmission rate and RTT variance (jitter) per path, together with the *shape* of the latency distribution — a small isolated cluster of slow requests hundreds of milliseconds out. The misleading signal is bandwidth utilisation, which can be low while loss is high.
SymptomSignalMeasurementHypothesisEvidenceRoot CauseChangeValidationRegression Check

One lost packet costs a timeout, not a packet

The intuition that loss costs "the time to send one more packet" is wrong by two orders of magnitude. When a packet is lost, the sender does not know immediately. Recovery happens either through duplicate acknowledgements triggering a fast retransmit — relatively quick, roughly an RTT — or, when there is not enough subsequent traffic to generate those duplicates, through a retransmission timeout, which is conservative by design and typically hundreds of milliseconds.

Small requests are the worst case. A large transfer has plenty of packets in flight to trigger fast retransmit; a small request-response may have so few that a single loss can only be recovered by the timeout. So the requests that suffer most from packet loss are frequently the smallest and most latency-sensitive ones, which is precisely the opposite of what most people expect.

The result is a distribution with a small, distant cluster: nearly all requests normal, a few hundreds of milliseconds out. Averages absorb this completely, which is why loss-induced tail latency is one of the clearest illustrations of the argument in The Average Was Fine and Users Were Not.

Request latency with ~0.3% packet loss on the pathILLUSTRATIVE
142020
418040
296060
98080
310100
42250
88500
201000
p50 41 ms — Completely unaffected — the median request lost nothingmean 52 ms — Barely moved; a handful of slow requests averages awayp99 480 ms — Dominated by requests that waited out a retransmission timeoutp99.9 940 ms — Requests unlucky enough to lose more than one packet

Why distributed systems see jitter a single machine never does

A function call within one process has essentially no variance. The moment a call crosses a network, its latency becomes a distribution shaped by queueing at every switch and router on the path, by competing traffic from systems you do not control, by scheduling on the receiving host, and by loss and recovery. That variance — jitter — is not noise around a true value; it is the actual behaviour.

Jitter compounds badly with fan-out. If each of seven parallel calls is usually fast but occasionally jittery, the request takes the maximum, so it inherits the worst jitter of the seven on every request. This is the amplification from Fan-Out: Waiting for the Slowest of Seven, now driven by network variance rather than by dependency slowness, and it is why distributed systems have tails that look qualitatively different from monolithic ones.

The diagnostic value of jitter is that it discriminates. Application-level slowness usually correlates with something — an endpoint, a tenant, a payload size, a time of day. Loss-induced tails correlate with a *path*: a particular availability zone pair, a particular provider link, a particular client network. If the slow requests have no application-level pattern but cluster by network path, that is the signal.

Distinguishing network-induced tails from application-induced tailsILLUSTRATIVE
SignalValueWhat it tells youVerdict
p50 latencyunchangedThe median path is healthy — rules out broad application slownessnormal
p99 latency110ms → 480msA small population of requests is very slowsmoking gun
Bandwidth utilisation22%Low. Loss is not caused by saturating your own linknormal
TCP retransmission rate0.02% → 0.31%The direct evidence: packets are being lost and recoveredsmoking gun
RTT variance (jitter) on path±3ms → ±140msHighly variable path, consistent with queueing or loss upstreamsmoking gun
Slow requests by endpointevenly spreadNo application pattern — points away from code and toward the pathsuspect
Slow requests by AZ pair94% on one pairA path-shaped correlation. This is the actual scopesmoking gun

What you can and cannot fix

Loss on a link you own or pay for is an operational problem with an operational fix: identify the segment, escalate to whoever runs it, and route around it while they work. Loss on the public internet between your edge and a user's device is not yours to fix at all, and pretending otherwise wastes time. The first useful question is therefore always "which segment", not "how do we fix it".

What you can always do is reduce your exposure. Fewer round trips on the critical path means fewer chances to lose a packet at a moment that costs a timeout — connection reuse, avoiding handshakes on hot paths, and collapsing chatty exchanges all shrink the target. Protocol choice matters too: transports with better loss recovery semantics can avoid head-of-line blocking that makes one lost packet stall unrelated concurrent work.

And you can stop letting a small tail become a large one. A per-call timeout with a fallback bounds the damage from a lost packet to the timeout value rather than to whatever the retransmission timer decides. Retrying is legitimate for idempotent calls, and must be bounded and jittered — a retry on a congested path adds to the congestion that caused the loss, which is the feedback loop in Retry Storms: The Load You Generated Yourself.

Loss and jitter: where it comes from and what you can do
SourceYours to fix?What helps
Your own link saturatedYesReduce traffic, increase capacity, apply backpressure — this is congestion you caused
A failing NIC, cable or switch in your infrastructureYesFind the segment, replace it; route around it meanwhile
Provider backbone or peering congestionPartlyEscalate with evidence; shift traffic to another path or region
Public internet between edge and userNoMove the edge closer to the user; reduce round trips; accept the rest
Wireless / mobile last mileNoFewer round trips, tolerant timeouts, graceful degradation

Key points

  • A lost packet typically costs a retransmission timeout — hundreds of milliseconds — not the time to resend one packet.
  • Small request-response exchanges suffer most, because they lack the in-flight packets needed to trigger faster recovery.
  • Loss-induced latency is invisible at p50 and mean, and dominates p99; the distribution shows a small distant cluster.
  • Jitter correlating with a network path rather than an endpoint or tenant is the signal that separates network causes from application ones.
  • Fan-out amplifies jitter: a request waiting on N calls inherits the worst variance of all N.

Follow the diagnosis

The causal chain, hop by hop — and the readings that invite the wrong conclusion.

  1. 1
    Client → server: a small request is sent; one packet is lost at a congested hop on the path.
  2. 2
    Sender → recovery: too few packets are in flight to generate duplicate acknowledgements, so fast retransmit cannot trigger.
  3. 3
    Sender → timer: recovery falls to the retransmission timeout, which is deliberately conservative and costs hundreds of milliseconds.
  4. 4
    Request → distribution: this request lands far out in the tail while every other request that second is unaffected.
  5. 5
    Fan-out → user: because the page waits on seven such calls, it inherits the worst of them, so the user-visible tail is worse than any single service's.
What this evidence makes people conclude — wrongly
  • "Bandwidth is fine, so the network is fine." Loss can be high while utilisation is low; the congestion may be someone else's.
  • "Average latency is unchanged, so nothing happened." Loss-induced tails are invisible in the mean by construction.
  • "A lost packet just costs one more packet." It costs a recovery cycle, usually a conservative timeout.
  • "It is intermittent, so it is not reproducible or actionable." It is highly reproducible when segmented by path.
  • "Retries will smooth it out." Unbounded retries on a congested path add to the congestion that caused the loss.

Measure, fix, validate

An optimization is not finished until the metric that motivated it has moved.

How to measure it
  • • TCP retransmission rate per path or availability-zone pair, which is the direct evidence of loss.
  • • RTT variance (jitter) per path, not just mean RTT — the variance is the diagnostic quantity.
  • • The latency distribution shape, looking for an isolated cluster hundreds of milliseconds out rather than a general shift.
  • • Correlation of slow requests against network path (AZ pair, provider, client network) versus application dimensions (endpoint, tenant, payload).
  • • Bandwidth utilisation, specifically to rule out self-inflicted congestion before escalating to a provider.
What actually fixes it
  • • Establish which segment is losing packets before proposing anything — your link, your hardware, the provider backbone, or the last mile.
  • • Reduce round trips on the critical path: reuse connections, avoid repeated handshakes, and collapse chatty exchanges.
  • • Bound the damage with per-call timeouts and fallbacks so a lost packet costs the timeout rather than the retransmission timer.
  • • Use idempotent, bounded, jittered retries where the call is safe to repeat, and never unbounded ones.
  • • Move the edge closer to users so the uncontrollable portion of the path is as short as possible.
How you know it worked
  • • p99 and p99.9 for the affected path, compared against the same window before — p50 will not move and should not be expected to.
  • • Retransmission rate on the path, which should fall if the cause was addressed rather than merely mitigated.
  • • The latency distribution shape, confirming the distant cluster shrank rather than shifted.
  • • Timeout and fallback rates, so a bounded tail achieved by degrading is visible as such rather than mistaken for a genuine improvement.
What it costs
  • • Aggressive timeouts bound the tail and cause spurious failures for legitimately slow requests.
  • • Retries improve success rates on lossy paths and add load to a path that may be lossy because it is congested.
  • • Moving the edge closer to users costs infrastructure and adds a distributed state problem.
  • • Protocol changes to improve loss behaviour require client support and change the operational and debugging surface.
Stop it coming back
  • An alert on retransmission rate and RTT variance per path, independent of latency alerts.
  • A p99 SLO per client region or availability-zone pair, so a path degradation is visible without being averaged away.
  • Synthetic probes between critical region pairs, providing a signal that does not depend on organic traffic.
  • A dashboard splitting slow requests by network path alongside the usual application dimensions.

Accuracy

Performance numbers are conditional. These are the conditions.

What these numbers depend on
  • ILLUSTRATIVEThe distribution and signal values are invented to show the characteristic shape of a loss-induced tail — a small distant cluster with an unmoved median.
  • ENVIRONMENT-SPECIFICRetransmission timeout values, recovery behaviour and jitter depend on the transport implementation, its configuration, and the specific network path. Measure your own paths.

Misconceptions

Claim
“Packet loss costs the time to send one more packet.”
Reality
Recovery costs a round trip at best and a conservative retransmission timeout at worst — commonly hundreds of milliseconds, especially for small exchanges.
Claim
“If bandwidth is not saturated, the network is not the problem.”
Reality
Loss frequently comes from congestion elsewhere on the path. Utilisation of your own link says nothing about a congested hop three networks away.
Claim
“Intermittent slowness with no pattern is unactionable.”
Reality
It has a pattern — a network path. Segment slow requests by AZ pair, provider and client network rather than only by endpoint and tenant.

Apply it