Skip to content

title: Networking description: The request path before your code: DNS, TCP, TLS, then the load balancer.


Networking

The request path before your code: DNS, TCP, TLS, then the load balancer.


Why This Exists

Before a single line of your code runs, a request has already spent most of its life elsewhere: resolving a name, opening a connection, negotiating encryption, and being routed by a load balancer.

This matters because the slowest part of a fast request is usually setup, not work. A handler that executes in 5 ms can sit behind 150 ms of DNS lookup, TCP handshake, and TLS negotiation. Optimizing the handler is then pointless — you are tuning 3% of the latency.

It also matters because these layers produce the confusing incidents. "The service is up and healthy, but 5% of users get connection errors" is never an application bug. It is a connection pool, a keepalive timeout, a stale DNS entry, or a load balancer health check — and if you have no model of this layer, you cannot debug it.


Mental Model: What a Request Actually Costs

Cold request (nothing cached, nothing pooled)
─────────────────────────────────────────────
DNS lookup            ~20-120 ms   (cacheable — often 0)
TCP handshake          1 RTT       (~1 ms local, ~150 ms cross-continent)
TLS 1.3 handshake      1 RTT       (~1 ms local, ~150 ms cross-continent)
─────────────────────────────────
Setup subtotal:        2 RTT + DNS      ← paid BEFORE your code runs
Your handler           ~5 ms

Cross-continent, that setup is ~300 ms against a 5 ms handler. Warm — DNS cached, connection pooled and kept alive — setup drops to roughly zero and the same request takes 5 ms.

This is why connection pooling is not a micro-optimization

Reusing a connection eliminates 2 round trips per request. At 150 ms per RTT, pooling is a 60× improvement on the critical path — far larger than anything you will achieve inside the handler.


The Three Things That Cause Real Incidents

1. Connection pool exhaustion. Your pool holds 100 connections. A downstream dependency slows from 10 ms to 2 s. Now each connection is held 200× longer, the pool drains, and requests queue for a connection that never frees. Your service reports itself healthy while every request times out. The pool size and the downstream timeout are a single coupled decision — this is Little's Law in operational form:

"""Little's Law applied to a connection pool: L = λ × W."""


def required_pool_size(arrival_rate_rps: float, latency_seconds: float,
                       headroom: float = 2.0) -> int:
    """Concurrency in flight = arrival rate × time each request holds a slot."""
    return int(arrival_rate_rps * latency_seconds * headroom)


if __name__ == "__main__":
    rps = 500
    for latency_ms in (10, 100, 2000):
        need = required_pool_size(rps, latency_ms / 1000)
        status = "OK" if need <= 100 else "POOL EXHAUSTED (configured: 100)"
        print(f"{rps} rps @ {latency_ms:>5} ms → need {need:>5} connections   {status}")
500 rps @    10 ms → need    10 connections   OK
500 rps @   100 ms → need   100 connections   OK
500 rps @  2000 ms → need  2000 connections   POOL EXHAUSTED (configured: 100)

Nothing about your traffic changed — only the downstream got slower — and a pool sized correctly for 100 ms is 20× too small at 2 s. This is why timeouts are mandatory: an unbounded timeout means an unbounded pool requirement.

2. Stale DNS. You failed over to a new IP; clients cached the old one for the TTL and keep hammering a dead host. DNS TTL is the floor on your failover time, and many clients ignore TTLs entirely.

3. Health checks that lie. A /health endpoint returning 200 OK because the process is alive — while its database connection is dead — keeps a broken instance in rotation. A health check must verify the dependencies the service actually needs.


Load Balancing: Layer 4 vs Layer 7

L4 (transport) L7 (application)
Sees IP + port Full HTTP: path, headers, cookies
Can route on Connection tuple URL, host, header, user
Cost Very low — packet forwarding Higher — terminates and parses
Enables Raw throughput Path routing, retries, TLS termination, canaries

L4 is a traffic cop; L7 is a receptionist that reads your request and decides where it belongs. Modern API gateways are L7 because everything interesting — routing /api/v2 to a new service, sticky sessions, per-route rate limits — requires reading the request.

The algorithm matters less than people think, with one exception: least-connections beats round-robin whenever request costs vary, because round-robin cheerfully sends request #101 to the instance already stuck on ten slow queries.


Pages in This Section

Page Status
HTTP & TCP Complete + DNS/TCP sims
Load Balancing Complete + visualizer
Proxies, Ingress, and Egress Complete — Forward/reverse proxies, K8s Ingress, egress control, AAA, payment gateway case study
Cloud Load Balancers Complete — AWS NLB/ALB/GWLB, GCP Cloud LB, Azure LB/AppGateway, cost comparison, pitfalls
Modern Protocols & Service Mesh Complete — HTTP/3 (QUIC), gRPC, Istio, Linkerd, mTLS, zero-trust, eBPF observability
gRPC vs HTTP in K8s & Load Balancing Complete — Connection models, service discovery, hotspot problem, DNS caching, skewed traffic, failure modes, solutions
Real-Time Communication Complete — WebSockets vs. long polling vs. SSE vs. webhooks, connection cost, event fan-out, reconnect behavior

HTTP & TCP walks the full request path with DNS-resolution and TCP-lifecycle simulators, plus HTTP/1.1 vs 2 vs 3 and head-of-line blocking.

Load Balancing covers the algorithms with a visualizer where you can kill a backend and watch traffic redistribute.

Proxies, Ingress, and Egress explains forward proxies (client-side), reverse proxies (server-side), Kubernetes Ingress controllers, egress gateways, and AAA (authentication/authorization/accounting) at the proxy layer. Includes a payment API gateway case study.

Cloud Load Balancers deep-dives AWS NLB (L4, extreme throughput), ALB (L7, HTTP routing), and GWLB (appliances), plus GCP and Azure equivalents. Covers when to use each, cost comparison, and real-world pitfalls.

Modern Protocols & Service Mesh covers latest developments in networking: HTTP/3 (QUIC) for mobile and packet-loss networks, gRPC for inter-service performance, Istio vs Linkerd service meshes with mTLS and distributed tracing, zero-trust networking (SPIFFE identity), BBR congestion control, and eBPF-based observability (Cilium/Hubble).

gRPC vs HTTP in K8s & Load Balancing is the deep-dive on production issues: HTTP/1.1 pools connections (distributed), HTTP/2 multiplexes one connection (hotspot), gRPC reuses connections (hotspot). K8s service discovery: DNS caching defeats load balancing. kube-proxy iptables/IPVS behavior. L4/L7 load balancer implications. Why traffic gets skewed (DNS round-robin + connection reuse). Failure modes (slow backend cascades, connection leaks). Solutions: service mesh (Istio/Linkerd), client-side load balancing, connection pooling. Interview-focused with real scenarios.


Key Takeaways

  • Setup often dominates work. 2 RTT + DNS is paid before your handler starts.
  • Connection pooling removes 2 RTT per request — the highest-leverage networking change you can make.
  • Pool size = arrival rate × latency. When a dependency slows down, your pool silently becomes too small.
  • Every remote call needs a timeout, or your pool requirement is unbounded.
  • Liveness = is this process wedged? Do not ping Redis/DB on liveness (restart storms). Readiness = can I take traffic? Check a dependency on readiness only if the instance is useless without it; optional caches should degrade, not go unready.
  • L7 buys routing intelligence; L4 buys raw speed. Use least-connections when request costs vary.