Day 48 Hard Networking TCP/UDP · QUIC DNS · TLS · LB

Networking Fundamentals — How Many Round Trips Does One Request Really TakeTCP/UDP, HTTP/1.1·2·3·QUIC, DNS, TLS Handshake, L4 vs L7 Load Balancing

Problem Scenario + Requirements

You're designing the ingress layer for a global API platform: 50M users across five continents, ~70% mobile, packet loss routinely 1–3%; API p99 target < 150ms, of which connection setup (DNS + TCP + TLS) often eats half of mobile time-to-first-byte. A request from Tokyo to a US-West data center already costs ~120ms of physical round-trip time (RTT); if the handshake takes 3 RTTs, that's 360ms before a single byte arrives.

The problem isn't "add servers"—it's that every RTT must be squeezed: how DNS steers a user to the nearest healthy DC, whether the transport is TCP or UDP, how to cut the handshake from 3 RTTs to 0, and whether load balancing happens at layer 4 or layer 7. These four decisions dominate first-packet latency, and in architect interviews they separate "can configure Nginx" from "understands the network."

High-Level Architecture (a request's network path)

graph LR
    U["Client
mobile/browser"] DNS["DNS resolve
GeoDNS + Anycast"] EDGE["Edge / L4 LB
Anycast VIP · DSR"] L7["L7 LB / Gateway
TLS terminate · route"] APP["App cluster"] U -->|"① name→IP"| DNS U -->|"② TCP/QUIC handshake"| EDGE EDGE -->|"③ TLS + HTTP"| L7 L7 -->|"④ keep-alive pool"| APP classDef client fill:#1a2530,stroke:#64c8ff,color:#e8eef5 classDef edge fill:#0e2030,stroke:#5eead4,color:#e8eef5 classDef app fill:#2a1530,stroke:#ff7ab6,color:#e8eef5 class U client class DNS,EDGE,L7 edge class APP app

DNS picks the DC; L4 picks the edge box; L7 terminates TLS and routes by path/tenant; origin reuses long-lived connections to skip handshakes

Each component owns a slice of latency: DNS steers users nearby (Anycast lets BGP decide "nearest"); L4 LB forwards by the connection 4-tuple—very high throughput but blind to content; the L7 LB/gateway terminates TLS and, seeing the HTTP message, does path routing, rate limiting, canary; the origin uses a keep-alive pool to amortize handshakes. Understanding this chain is how you locate "which RTT is slow."

Key Technical Points

1. TCP vs UDP: reliable & ordered vs connectionless & low-latency

Principle: TCP provides a reliable, ordered, connection-oriented byte stream—three-way handshake to connect, sequence numbers + ACK retransmit for loss, sliding window for flow control, congestion control (Cubic/BBR) to avoid overwhelming the network. The cost: 1 RTT to connect, and "ordered" delivery is a double-edged sword—if one packet is lost, already-arrived bytes must wait in the kernel buffer for the retransmit. That is the root of head-of-line (HOL) blocking. UDP is connectionless datagrams: fire-and-forget, no ordering, no congestion control—zero handshake, zero HOL blocking, but reliability/ordering/congestion are all the application's problem.

Trade-off:
# Selection intuition: first ask "if one packet is lost, can the app tolerate it?"
lossy-OK, want latest   → UDP (voice: a dropped frame beats a stale one)
lossless, need ordering → TCP / QUIC (API, files, payments)
ordered but fear HOL blocking + frequent mobile network switch → QUIC (HTTP/3)
Real-world:

2. HTTP evolution: three sieges against head-of-line blocking

Principle: HTTP's performance history is essentially three wars against HOL blocking. HTTP/1.1 processes one request at a time per connection (pipelining was practically abandoned), so browsers open 6 parallel TCP connections—connection-level HOL blocking. HTTP/2 does application-layer multiplexing over one TCP connection: requests are sliced into frames tagged with stream IDs and interleaved, solving application-layer HOL blocking—but all streams still share one TCP byte stream, so a single lost TCP packet stalls every concurrent stream (transport-layer HOL blocking remains, and hurts more than HTTP/1.1 because the losses of 6 connections are compressed into 1). HTTP/3 drops TCP entirely and runs over QUIC: each stream has its own sequence space, so loss only stalls its own stream while the rest keep flowing—HOL blocking is fully solved.

Three generations:
HTTP/1.1HTTP/2HTTP/3
TransportTCPTCPQUIC / UDP
Concurrencymultiple connssingle-conn muxsingle-conn mux
HOL blockingconnection-level (severe)TCP transport stillnone (per-stream)
Setup RTTTCP+TLS 2–3TCP+TLS 2–31 (incl. TLS) / 0-RTT
Headersplaintext, repeatedHPACK compressedQPACK compressed
Network switchbreaksbreaksconnection migration

Key insight: on high-loss mobile/transoceanic links, HTTP/2 may underperform HTTP/1.1's multiple connections—putting all eggs in one TCP basket means one jolt spills them all. That's exactly why you must descend to QUIC: HOL blocking lives in the transport layer, so bumping the HTTP version alone can't cure it—you have to change the transport.

Real-world:

3. TLS handshake: from 2-RTT to 0-RTT, and the replay trap

Principle: TLS layers encryption over plaintext TCP. TLS 1.2 needs 2 RTTs (ClientHello→ServerHello+cert→key exchange→Finished) before the first byte; stacked on TCP's 1 RTT that's 3 RTTs. TLS 1.3 cuts a negotiation round to just 1 RTT; for returning users a session ticket enables 0-RTT: the client puts application data (early data) in the very first packet, running handshake and data in parallel. QUIC goes further, merging the transport and TLS handshakes—1-RTT for new connections, 0-RTT for returning ones. Each saved RTT on a transoceanic link is tens to hundreds of milliseconds.

sequenceDiagram
    participant C as Client
    participant S as Server
    Note over C,S: TLS 1.2 (2-RTT)
    C->>S: ClientHello
    S->>C: ServerHello + Cert
    C->>S: KeyExchange + Finished
    S->>C: Finished ✓ only now can send data
    Note over C,S: TLS 1.3 0-RTT (resumption)
    C->>S: ClientHello + early data (request already here)
    S->>C: response + Finished
The cost of 0-RTT = replay attacks: early data is sent before the handshake completes, so the server cannot tell whether an attacker recorded and replayed it. Therefore 0-RTT data may only carry idempotent operations (GET, idempotent queries), never a non-idempotent write like POST /transfer—otherwise one transfer could be replayed into two. In practice: browsers only send GET in 0-RTT; servers force a full 1-RTT handshake for non-idempotent requests.

Real-world:

4. DNS + L4 vs L7 load balancing: getting traffic to the nearest healthy DC

Principle: before a request goes out it needs DNS resolution—a recursive resolver walks root→TLD→authoritative servers, relying on TTL caching to avoid the full walk each time. Going global uses two tricks: GeoDNS returns different IPs by resolver geography; Anycast announces the same IP from many locations, letting BGP route each user to the network-nearest node. Once traffic reaches a DC, load balancing splits into two layers: L4 (transport) only sees the IP:Port 4-tuple and forwards by connection—no decryption, extreme throughput, often with DSR (return path bypasses the LB); L7 (application) terminates TLS and parses HTTP, so it can route by URL path/header/cookie, do canary, rate limiting, WAF—at the cost of decrypting and parsing every connection.

L4 vs L7:
L4 (transport)L7 (application)
SeesIP:Port 4-tuplefull HTTP (URL/header/cookie)
Can doconnection forwarding, DSRpath routing, canary, rate limit, TLS terminate
Throughput/costvery high / lowlower / high (decrypt+parse)
TypicalLVS, AWS NLB, MaglevNginx, Envoy, AWS ALB
# Common production combo: Anycast → L4 (volume+balance) → L7 (smart routing)
Client --DNS/Anycast--> [L4 LB: consistent hash picks edge, DSR return]
                         --> [L7 gateway: terminate TLS, /v2/* canary 5%]
                             --> App pool (keep-alive reuse, no re-handshake)
# L4 uses consistent hashing so existing connections aren't reshuffled when backends change (see Day 4)
Real-world:

Scaling & Optimization

Common Pitfalls + Interview Questions

1. "Just upgrade to HTTP/2 and it's faster": on high-loss mobile networks, HTTP/2's single connection is dragged by TCP HOL blocking and may lose to HTTP/1.1's multiple connections. The real fix descends to QUIC/HTTP/3—HOL blocking is in the transport, not the application.
2. Sending writes over 0-RTT: early data can be replayed; non-idempotent operations (transfers, orders) must never use 0-RTT, or one operation is replayed into two.
3. DNS TTL set too long: during failover the stale IP stays cached until the TTL expires—minutes of downtime. Disaster-recovery scenarios want short TTLs (30–60s), but too short adds resolution load—a trade-off.
4. Terminating TLS in one place but ignoring the internal network: if the edge terminates TLS and talks plaintext to the origin, a breached internal network can eavesdrop. Under zero trust the internal hop needs mTLS too.

Frequent follow-ups:

  1. Draw an HTTPS request from typing the domain to receiving the first byte—which RTTs are involved, and how do you save each?
  2. HTTP/2 already multiplexes—why HTTP/3? How exactly does TCP HOL blocking occur?
  3. L4 vs L7 LB: which layer, what can each see, and when must you use L7?
  4. How does Anycast achieve "nearest"? What's its hazard for long-lived TCP connections (route flap switches nodes)?
  5. Why is TLS 1.3 0-RTT fast, and why dangerous? How would you enable it safely at the gateway?

Deeper Resources

Deep-Dive Questions (click to expand)

1. Tokyo user to a US-West DC: how many RTTs for a cold HTTPS request (HTTP/2)? What does HTTP/3 + 0-RTT cut it to? Assume 120ms transoceanic RTT.

HTTP/2 cold start: DNS (≈0 on local cache hit, else 1 RTT) + TCP three-way handshake 1 RTT + TLS 1.3 handshake 1 RTT before the first request goes out, then 1 RTT to fetch the response. Excluding DNS ≈ 3 RTTs ≈ 360ms; add ~1 RTT if DNS misses.

HTTP/3 first time: QUIC merges transport and TLS handshakes into 1 RTT, plus 1 RTT for the response ≈ 2 RTTs ≈ 240ms—one whole RTT saved.

HTTP/3 returning 0-RTT: the request rides the first packet (early data), response returns 1 RTT later ≈ 120ms—from 360ms to 120ms, the difference between "spinning" and "instant" on a transoceanic link.

This is why big players fight over handshake RTTs: the speed of light is non-negotiable; the only thing you can cut is the number of round trips.

2. Why is HTTP/2's HOL blocking "more subtle yet more painful" than HTTP/1.1's? How do you decide whether to adopt HTTP/2?

In the HTTP/1.1 era browsers opened 6 TCP connections; one stalling left the other 5 running—loss was diffused. HTTP/2 compresses all requests into one TCP connection; the application appears concurrent, but once the underlying byte stream drops a packet, TCP must deliver in order, so all streams wait for the retransmit together. On low-loss links multiplexing wins net; on high-loss links, one basket for all eggs is more fragile.

Deciding: look at your users' network quality. Intra-DC/low-loss → HTTP/2's gain is clear; heavy mobile-weak/transoceanic → either keep multiple connections or go straight to HTTP/3 (QUIC's per-stream independence is immune at the root). Don't chase "higher version number" alone.

3. Anycast makes "the same IP answered by the nearest node," but what risk does it hide for long-lived TCP connections? Why does UDP/QUIC fit Anycast better?

Risk: Anycast's "nearest" is decided by BGP routing, which flaps with network conditions. If a long-lived TCP connection has its subsequent packets rerouted mid-flight by BGP convergence to another node, that node holds no state for the connection (seq, TLS session), so the connection is RST-reset. Traditionally Anycast suits short, stateless exchanges (DNS's one-question-one-answer).

Why QUIC fits: QUIC identifies a connection by a connection ID rather than the 4-tuple, and supports connection migration—even if packets land on a new node, or the client changes IP/port, the connection ID lets it reclaim the connection without re-handshaking. This makes Anycast + long-lived connections truly viable for the first time, and is also why mobile devices survive a WiFi/cellular switch without dropping.

4. L7 LB must terminate TLS to read HTTP—what does that cost in latency, security, and money? Why do big players run L4+L7 rather than pure L7?

Cost of terminating TLS: every connection is decrypted at L7, high CPU cost, per-box throughput far below L4; and plaintext appears in the LB's memory, becoming an attack surface (keys, request bodies concentrated). The gain is routing by URL/header, canary, rate limiting, WAF—impossible without decryption.

Why two layers: L4 carries volume, L7 carries intelligence. L4 (Maglev/LVS/NLB) uses consistent hashing to spread massive connections evenly across a fleet of L7s, itself never decrypting, with extreme throughput, and can run DSR so return packets bypass the LB straight to the client (saving half the bandwidth); each L7 only handles its share for fine-grained routing. Pure L7 at the very front is both expensive and hard to scale horizontally, and pipes DDoS straight into the decryption layer. Layering means "coarse-filter first, fine-tune second."

5. "Saving RTTs" recurs throughout. String together the RTT-saving techniques across DNS, TCP, TLS, HTTP, and note which carry a consistency/security cost.
  • DNS: TTL caching skips the full recursive walk; dns-prefetch. Cost: long TTL slows failover.
  • TCP: keep-alive pooling amortizes handshakes over thousands of requests; TCP Fast Open lets SYN carry data. Cost: connection memory; TFO early data has the same replay surface.
  • TLS: 1.2→1.3 saves an RTT; session resumption/ticket skips the full handshake; 0-RTT sends data in the first packet. Cost: 0-RTT is replayable, idempotent requests only.
  • HTTP: multiplexing saves extra connection handshakes; HTTP/3 merges transport+TLS into 1-RTT, connection migration avoids re-handshake on network switch. Cost: QUIC user-space CPU overhead, some networks block UDP.
  • Topology: terminate TLS at the nearest edge (short handshake RTT) + long connection to origin (Day 28).

The through-line: the speed of light fixes a single RTT; architecture can only "reduce the number of round trips" and "amortize them," and every aggressive RTT saving (0-RTT/TFO) trades security or consistency for latency.