Resourcesnetworkrtttlsconnection poolretransmits

Network Signals: Is It the Network, or the Service on the Other End?

Connection setup can cost more than the request it carries. RTT, bandwidth, retransmits, handshake counts and pool waits each answer a different question — and the first one to answer is whether the network is involved at all.

Follow the diagnosis

Frame the diagnosis

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

Diagnostic question
Is the time going into the network itself, into establishing connections, or into a dependency that is simply slow to respond?
Symptom
Calls to a downstream service are slower than that service says it is. Its own latency chart shows 12ms; your client-side chart shows 180ms, and both teams believe their own numbers.
Signal
Client-observed duration minus server-reported duration is the reading that matters — it isolates everything between the two. Bandwidth utilization is the least useful signal here: most services are nowhere near their bandwidth limit and are still spending most of their time on the network.
SymptomSignalMeasurementHypothesisEvidenceRoot CauseChangeValidationRegression Check

The gap between "our latency" and "your latency"

When two teams disagree about how slow a call is, they are usually both right about different intervals. The server measures from first byte parsed to last byte written. The client measures from intent to response — which includes DNS, connection establishment, TLS negotiation, waiting for a free connection from the pool, the request in flight, the server's work, the response in flight, and any retry hidden inside the client library.

The most useful thing you can do is decompose that gap once, explicitly, with a budget. On a fresh connection, DNS plus TCP plus TLS is roughly three to four round trips before a single byte of your request is transmitted — on a 40ms RTT path that is well over 100ms of pure setup, dwarfing a 12ms server. On a reused connection it is approximately zero. So the single most consequential number in this whole area is often the connection reuse rate (Keep-Alive and Connection Reuse, Connection Pooling).

This is also why "the network is slow" is rarely the right conclusion. The path is usually fine; the client is opening a new connection per request, or the pool is too small and callers are queueing for one, or a retry is silently doubling the work. Each of those is fixed in your code, not by the network team.

One 12 ms call, on a fresh connection versus a reused one (RTT 40 ms)ILLUSTRATIVE
DNS resolution (uncached)Zero when the resolver cache is warm; a cold or misconfigured cache makes this recur constantly.25 ms
TCP handshake (1 RTT)Zero on a reused connection.40 ms
TLS handshake (1–2 RTT)Session resumption cuts this sharply; TLS 1.3 needs fewer round trips than 1.2.55 ms
Pool waitTime queued for a free connection — pure client-side queueing, invisible to the server.18 ms
Request in flightHalf an RTT plus serialization of the request body.20 ms
Server processingThe only part the downstream team can see, and the only part they are measuring.12 ms
Response in flightHalf an RTT plus transfer time, which grows with payload size.22 ms
Remaining8 ms left

RTT and bandwidth are different constraints

Bandwidth is how much data fits per second; latency is how long the first byte takes to arrive. They are not substitutes, and confusing them produces expensive non-solutions. A chatty protocol that makes nine sequential round trips is bounded by RTT, and upgrading the link from 1 Gbps to 10 Gbps changes nothing at all — the connection was never full. The fix is fewer round trips: batching, pipelining, multiplexing, or moving the caller closer (Bandwidth vs Latency).

Bandwidth becomes the constraint when payloads are large relative to the window, and it shows up as transfer time scaling with response size. That is when compression and payload trimming pay off, and it is the one case where a bigger pipe genuinely helps (Payload Size: 20KB, 200KB, 5MB, Compression: Cheaper Bytes, Not Fewer).

Between them sits a subtlety worth carrying: a single TCP connection cannot exceed roughly its window size divided by the RTT, regardless of available bandwidth. On a long-distance path this can cap one connection well below the link rate while parallel connections or a multiplexed protocol saturate it easily. So "the link is not full" and "this transfer is network-bound" are entirely compatible statements — one of the most common sources of talking past each other in a cross-team latency investigation (Cross-Region Latency Is Physics, Not Configuration).

Which network signal answers which question
SignalAnswersDoes not answer
RTT (ping / handshake time)Distance and path quality; the floor for any round tripWhether you are making too many round trips
Connection reuse rateWhether you pay setup on every call — often the largest single leverHow fast the remote service is
TLS handshakes per secondHow much of your CPU and latency is negotiationWhether the path itself is congested
Retransmit ratePacket loss on the path, which inflates the tail sharplyWhich hop is dropping — that needs a traceroute
Bandwidth utilizationWhether the pipe is genuinely full — usually it is notAnything about latency-bound workloads
Pool wait timeClient-side queueing for a connection — invisible to the serverWhether the remote service is slow (it may be, causing the queue)
Client duration − server durationEverything between the two, as one numberWhich component within that gap dominates

Loss, pools, and the tail

Packet loss is disproportionately a tail problem. A path with 0.5% loss has a perfectly normal median — most requests lose nothing — while the unlucky fraction waits for a retransmission timeout that can be orders of magnitude longer than the RTT. The result is a service whose p50 is excellent and whose p99 is dreadful, with no application-level explanation. Retransmit rate is the confirming signal, and it belongs on the dashboard of anything crossing a region boundary (Packet Loss: Duplicate ACKs, Fast Retransmit and the RTO, Packet Loss Buys You a Timeout, Not a Retransmit).

Connection pool exhaustion produces a similar-looking result with an entirely different cause. When every pooled connection is in use, further callers queue — and that queue time is not visible to the remote service, does not appear in its metrics, and is not network latency at all. It looks exactly like "the dependency got slow", and it is the same queueing shape as Connection Pool Saturation: Waiting in Front of an Idle Database on the database side. The distinguishing reading is pool wait time, which most HTTP clients can expose and almost nobody enables.

The practical sequence: measure the client-minus-server gap first, then decide which of the three families you are in. Setup cost points at connection reuse. Pool wait points at concurrency limits. Retransmits and RTT point at the path itself — and only then is it worth involving anyone about the network.

Client sees 180 ms, server reports 12 ms — where the other 168 ms wentILLUSTRATIVE
SignalValueWhat it tells youVerdict
Client duration − server duration168 msEverything between the two processes. The single most useful reading; decompose from here.smoking gun
Connection reuse rate4%Nearly every call pays DNS + TCP + TLS setup. This is the dominant term.smoking gun
TLS handshakes/s910/sMatches request rate almost exactly — confirming a new connection per request.smoking gun
RTT to dependency38 ms (stable)The path is fine. Distance is not the problem; the number of round trips is.normal
TCP retransmit rate0.02%Negligible loss — rules out a degraded path as the cause of the tail.normal
Bandwidth utilization3%The pipe is nowhere near full, which is the normal state and proves nothing.normal
Connection pool wait p992 msNot pool-starved. Rules out client-side queueing.normal

Key points

  • Client duration minus server duration isolates everything between the two processes — measure it before theorizing.
  • On a fresh connection, DNS + TCP + TLS is several round trips and routinely dwarfs the server's own work; connection reuse rate is usually the biggest lever.
  • RTT and bandwidth are different constraints: a chatty protocol is RTT-bound and a faster link changes nothing.
  • Packet loss is a tail problem — normal median, dreadful p99 — and retransmit rate is what confirms it.
  • Connection pool wait is client-side queueing that looks exactly like a slow dependency and is invisible in the dependency's metrics.

Follow the diagnosis

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

  1. 1
    Caller → client library: a call is issued; the library needs a connection from the pool.
  2. 2
    Pool → new connection: if reuse is disabled, the pool is too small, or keep-alive is not negotiated, the client opens a fresh connection instead of reusing one.
  3. 3
    New connection → handshakes: DNS, TCP and TLS consume several round trips before any request byte is sent — on a 40ms RTT path, over 100ms (The TLS Handshake).
  4. 4
    Handshakes → client-observed latency: the client records a duration that the server never sees, because the server's timer starts when the request arrives.
  5. 5
    Client latency → retries and timeouts: if the client timeout was set from the server's published latency, the setup cost pushes calls past it, and the retries multiply the handshake load (Retry Storms: The Load You Generated Yourself).
What this evidence makes people conclude — wrongly
  • "Bandwidth is at 3%, the network is fine" — most latency-bound workloads never approach bandwidth limits; the reading proves nothing.
  • "Their service is slow" — compare client and server durations before attributing; the gap is often entirely on your side of the wire.
  • "We need a faster link" — for a chatty, RTT-bound protocol, a bigger pipe changes nothing at all.
  • "p50 is fine so the network is fine" — packet loss lives entirely in the tail and leaves the median untouched.
  • "The pool is fine, utilization is 80%" — at 80% utilization callers are already queueing; pool wait time is the signal, not utilization (Queueing: Why Systems Get Slow Before They Get Broken).

Measure, fix, validate

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

How to measure it
  • • Client-side call duration and server-reported duration for the same span, and the difference as its own series.
  • • Connection reuse rate, and TLS handshakes per second compared against request rate.
  • • RTT to the dependency, sampled continuously rather than by hand during incidents.
  • • TCP retransmit rate on paths that cross availability zones or regions.
  • • Connection pool wait time (p50 and p99) and pool utilization from the HTTP client.
  • • Response payload size alongside transfer time, so bandwidth-bound calls are distinguishable from RTT-bound ones.
What actually fixes it
  • • Enable and verify connection reuse — keep-alive on, pool sized for concurrency, idle timeouts longer than your call interval. Usually the single biggest win.
  • • Reduce round trips rather than bytes when RTT-bound: batch calls, use a multiplexed protocol, or collapse a chatty sequence into one request ([[http2]], [[batch-apis]]).
  • • Size the connection pool from concurrency, not intuition — Little's Law gives the number ([[littles-law]], [[concurrency-limits]]).
  • • Reduce payload size when transfer time scales with response size ([[payload-size]], [[compression]]).
  • • Move the caller closer, or cache regionally, when RTT itself is the floor ([[cross-region-latency]]).
  • • Escalate to the network path only after retransmits or RTT actually show a problem.
How you know it worked
  • • Confirm the client-minus-server gap shrank at the same request rate — that gap was the target, so it is the proof.
  • • Confirm TLS handshakes per second fell well below request rate, showing reuse is genuinely happening.
  • • Confirm pool wait p99 is near zero after resizing, and that no new saturation appeared downstream ([[bottleneck-migration]]).
  • • Check p99 rather than only p50, since setup and loss costs concentrate in the tail.
What it costs
  • • Large connection pools consume file descriptors and remote-side resources; every client pool is a load decision for the server.
  • • Long keep-alive idle timeouts hold connections that may be idle for a long time, and can interact badly with load balancers that close them silently.
  • • Batching reduces round trips and increases per-call latency for the first item in the batch.
  • • Regional caching or replication removes RTT and introduces staleness and cost ([[replication-lag]]).
Stop it coming back
  • Keep connection reuse rate on the service dashboard — it silently regresses whenever a client library or proxy is reconfigured.
  • Alert on the client-minus-server gap, which catches any new cost between the processes regardless of cause.
  • Track pool wait p99 with an alert; pool exhaustion is a common and repeatable regression after concurrency changes.
  • Include a realistic-RTT environment in load testing, since local tests hide every round-trip cost (Benchmark Fallacies: Confident Numbers That Are Wrong).

Accuracy

Performance numbers are conditional. These are the conditions.

What these numbers depend on
  • ILLUSTRATIVEThe latency budget and signal panel are teaching examples. Handshake costs scale with the actual RTT of your path, and TLS 1.3 with session resumption behaves very differently from a cold TLS 1.2 negotiation.
  • ENVIRONMENT-SPECIFICRTT, loss rate and achievable per-connection throughput depend on the path: same-AZ, cross-AZ, cross-region and internet paths differ by orders of magnitude.
  • RUNTIME-SPECIFICWhether connection reuse, pool wait time and retry behavior are observable at all depends on the HTTP client library; defaults differ sharply and several popular clients do not expose pool wait without configuration.

Misconceptions

Claim
“Their service is slow — their own chart is wrong.”
Reality
Both charts are usually right about different intervals. The server measures from request-parsed to response-written; the client measures DNS, connection setup, TLS, pool wait, both flight times and any hidden retry. Compute the difference before attributing it to anyone.
Claim
“Bandwidth is at 3%, so the network is not the problem.”
Reality
Most latency-sensitive workloads never approach their bandwidth limit and are still dominated by network time. Round trips, not bytes, are the constraint — and a single connection is capped near window-over-RTT regardless of how empty the link is.
Claim
“The connection pool is fine, it is only 80% utilized.”
Reality
At 80% utilization callers are already queueing for connections some of the time, and that wait is invisible to the remote service. Pool wait time is the signal; pool utilization has the same blind spot as CPU utilization does for the run queue (Queueing: Why Systems Get Slow Before They Get Broken).

Apply it

Where the depth lives

Networking
Handshakes, windows and loss recovery

This lesson stops at "which reading tells me the network is involved". Why a handshake costs a round trip, why one connection cannot exceed window-over-RTT, and how loss recovery inflates the tail all live in the Networking domain.