Chapter 3: Networking Deep Dive
L4 vs L7, Connection Pooling, Keep-Alive, Head-of-Line Blocking
Introduction
Network behavior is the foundation of distributed systems. Most system design failures trace back to misunderstanding network fundamentals: TCP mechanics, connection lifecycle, protocol overhead, and failure modes.
This chapter covers the critical networking concepts you need for both building production systems and acing FAANG interviews. We go deep on Layer 4 vs Layer 7 proxying, connection pooling strategies, HTTP keep-alive mechanics, head-of-line blocking across protocols, and data center networking topologies.
Why This Matters for System Design
Understanding networking depth separates senior engineers from junior ones. When you design a system handling 100K RPS, knowing that each TCP connection costs 3-way handshake (1.5 RTT) + TLS handshake (2 RTT) = 3.5 RTT means you can calculate that without connection pooling, you'd spend 35ms just on connection setup for a 10ms cross-region RTT. That's 350ms of latency before any application logic runs.
L4: Transport Layer Networking
Layer 4 operates at the TCP/UDP level. It sees ports, sequence numbers, and connection state, but not application protocols like HTTP.
TCP vs UDP Multiplexing
Both TCP and UDP multiplex multiple application connections over a single network interface using port numbers.
Port 8080] A2[App 2
Port 8081] A3[App 3
Port 8082] end subgraph "Transport Layer" TCP[TCP Layer
Demultiplexes by port] end subgraph "Network Layer" IP[IP Layer
Single interface: eth0] end A1 --> TCP A2 --> TCP A3 --> TCP TCP --> IP IP --> N[Network
Packets with dst port] style TCP fill:#2374ab style IP fill:#1d5a7a
| Feature | TCP | UDP |
|---|---|---|
| Connection State | Stateful: SYN/ACK handshake, sequence numbers | Stateless: no connection setup |
| Reliability | Guaranteed delivery, retransmission | Best-effort, no retransmission |
| Ordering | In-order delivery guaranteed | No ordering guarantee |
| Overhead | 20+ bytes header, connection state memory | 8 bytes header, minimal state |
| Use Cases | HTTP, database connections, file transfer | DNS, video streaming (tolerates loss), QUIC foundation |
| Setup Latency | 1.5 RTT (3-way handshake) | 0 RTT (can send data immediately) |
Socket Buffers & Backlog Queue
Each TCP connection has kernel-managed buffers for sending and receiving data. The accept backlog queue holds completed connections waiting for application to accept().
Real Numbers: Socket Buffer Sizing
- Default TCP recv buffer: 128KB-256KB (Linux)
- Max TCP recv buffer: 4MB-16MB (tunable via sysctl)
- Accept backlog: 128-4096 connections typical
- SYN backlog: 1024-8192 (sysctl net.ipv4.tcp_max_syn_backlog)
SYN Flood Protection: SYN Cookies
SYN flood attacks send many SYN packets without completing the handshake, exhausting the SYN backlog. SYN cookies encode connection state in the sequence number, avoiding memory allocation.
Failure Mode: SYN Backlog Exhaustion
Symptom: New connections timing out, but existing connections work fine.
Diagnosis: netstat -s | grep "SYNs to LISTEN" shows drops.
Fix: Increase net.ipv4.tcp_max_syn_backlog or enable net.ipv4.tcp_syncookies=1.
TIME_WAIT State and Its Implications
When a TCP connection closes, the side that initiates the close (sends FIN first) enters TIME_WAIT state for 2*MSL (Maximum Segment Lifetime, typically 60-120 seconds). This prevents delayed packets from old connections from being misinterpreted.
to catch delayed packets Note over C: After 2*MSL, CLOSED
Real Numbers: TIME_WAIT Accumulation
- TIME_WAIT duration: 60s (Linux default)
- Max connections per IP:port pair: ~64K (ephemeral port range)
- Connections per second sustainable: 64K / 60s = ~1,000 conn/sec per server IP
- At 10K conn/sec: Need 10 server IPs or connection reuse
| Problem | Symptom | Solution |
|---|---|---|
| TIME_WAIT accumulation (client) | Cannot establish new connections: "Address already in use" | Enable SO_REUSEADDR, use connection pooling |
| TIME_WAIT accumulation (server) | Many connections in TIME_WAIT state | Usually harmless (server has many ports), but can tune tcp_tw_reuse |
| Port exhaustion | All ephemeral ports used | Increase net.ipv4.ip_local_port_range, use multiple IPs |
SO_REUSEPORT: Load Balancing Across Processes
SO_REUSEPORT (Linux 3.9+) allows multiple processes to bind to the same port. The kernel distributes incoming connections across them, enabling per-process accept queues.
listen on :80] L1 --> W1[Worker 1
accept from shared queue] L1 --> W2[Worker 2
accept from shared queue] L1 --> W3[Worker 3
accept from shared queue] Note1[Single accept queue
Thundering herd on new connection] end subgraph "With SO_REUSEPORT" W4[Worker 1
listen on :80] W5[Worker 2
listen on :80] W6[Worker 3
listen on :80] K[Kernel] -->|Load balances| W4 K -->|Load balances| W5 K -->|Load balances| W6 Note2[Per-worker accept queue
No thundering herd] end style W4 fill:#5cb85c style W5 fill:#5cb85c style W6 fill:#5cb85c
Why This Matters for System Design
Without SO_REUSEPORT, Nginx or Go HTTP servers must use a shared accept queue, causing all worker processes to wake on each connection (thundering herd). With SO_REUSEPORT, the kernel distributes connections directly to workers, improving CPU cache locality and reducing context switches.
L7: Application Layer Networking
Layer 7 operates at the application protocol level (HTTP, gRPC, etc.). It can parse headers, route based on URL paths, terminate SSL, and cache responses.
HTTP Parsing and Header Inspection
L7 proxies must parse the entire HTTP request to make routing decisions. This adds CPU cost and latency compared to L4.
Host: example.com
Cookie: session=abc Note over L7: Parse HTTP headers
Extract: method, path, headers Note over L7: Routing decision based on path L7->>B1: GET /api/users HTTP/1.1
X-Forwarded-For: 1.2.3.4 B1->>L7: HTTP/1.1 200 OK
Content-Type: application/json Note over L7: Can inspect response
Cache, compress, log L7->>C: HTTP/1.1 200 OK
Content-Type: application/json
Real Numbers: L7 Parsing Overhead
- HTTP parsing CPU cost: 5-20 microseconds per request (depends on header size)
- Latency added: 0.1-1ms (buffering + parsing + routing decision)
- Memory per connection: 4-16KB for request/response buffers
- Throughput: Nginx ~100K req/sec per core, Envoy ~50K req/sec (more features)
SSL Termination at L7
L7 proxies can terminate TLS, decrypt traffic, inspect it, then re-encrypt to backends. This enables caching, compression, and WAF (Web Application Firewall) inspection.
Encrypted| L7[L7 Proxy] L7 -->|Decrypts| P[Plaintext
HTTP] P --> I1[Inspect headers] P --> I2[Cache check] P --> I3[WAF rules] P --> I4[Compress response] L7 -->|TLS to backend
or plaintext| B[Backend] style P fill:#f0ad4e style L7 fill:#5bc0de
| SSL Termination Strategy | Security | Performance | Use Case |
|---|---|---|---|
| Terminate at L7, plaintext to backend | Low (plaintext in datacenter) | High (single TLS handshake) | Trusted datacenter, need L7 features |
| Terminate at L7, re-encrypt to backend | High (end-to-end encryption) | Medium (double TLS overhead) | Compliance requires encryption everywhere |
| SSL passthrough (L4) | High (proxy never sees plaintext) | High (no parsing) | Cannot inspect/cache/route by content |
Content-Based Routing
L7 proxies route based on HTTP headers, URL paths, cookies, or even request body content.
Interview Signal
Mentioning content-based routing shows depth: "We use Envoy to route /graphql to our GraphQL gateway and /rest/* to legacy services. We also inspect the X-API-Version header to gradually migrate traffic to v2 APIs without client changes."
L4 vs L7 Load Balancing: Deep Comparison
This is a critical system design decision. L4 is faster but less flexible; L7 is feature-rich but adds overhead.
Feature Comparison
| Aspect | L4 Load Balancer | L7 Load Balancer |
|---|---|---|
| Protocol Awareness | None (sees TCP/UDP streams) | Full HTTP/gRPC/WebSocket parsing |
| Routing Decisions | IP, port, round-robin, least-connections | URL path, headers, cookies, query params, request body |
| Session Affinity | Source IP hashing (brittle with NAT) | Cookie-based (reliable) |
| SSL Termination | Passthrough only (cannot inspect) | Can terminate, inspect, re-encrypt |
| Latency Added | ~0.01-0.1ms (minimal) | 0.5-2ms (buffering + parsing) |
| Throughput | 10-100 Gbps per instance | 1-10 Gbps per instance (CPU bound) |
| Caching | Not possible | Can cache HTTP responses |
| WAF / Rate Limiting | Limited (TCP rate limits only) | Full application-layer rules |
| Health Checks | TCP connect or UDP packet | HTTP GET /health with response validation |
| Cost | Lower (less CPU, simpler) | Higher (more CPU, more memory) |
Direct Server Return (DSR)
With DSR, the load balancer forwards requests to backends, but backends respond directly to clients, bypassing the LB. This asymmetric routing avoids LB becoming a bottleneck for response traffic.
configured on loopback B->>C: SYN-ACK from 10.0.0.1 C->>B: ACK to 10.0.0.1 (routed directly to backend!) Note over C,B: All subsequent packets bypass LB C->>B: HTTP Request B->>C: HTTP Response (large!) Note over LB: LB only sees inbound SYN
Response traffic does NOT go through LB
Real Numbers: DSR Impact
- Response traffic: 10-100x larger than request (HTML, images, JSON responses)
- LB bandwidth savings: ~90% (only handles inbound requests, not responses)
- Constraint: L4 only, requires backend to own VIP on loopback interface
- Use case: Video streaming, large file downloads, high-bandwidth APIs
Why This Matters for System Design
If you're designing a video streaming service, DSR is critical. Without it, if your LB handles 10 Gbps inbound (requests), it must also handle 500 Gbps outbound (video streams), requiring massive LB capacity. With DSR, the LB only sees 10 Gbps.
| Scenario | Use L4 | Use L7 |
|---|---|---|
| Single backend service, need max throughput | β (minimal overhead) | β (overkill) |
| Route /api vs /static to different services | β (cannot see URL) | β (content routing) |
| Need SSL inspection for security | β (passthrough only) | β (terminate SSL) |
| Sticky sessions based on cookies | β (source IP unreliable) | β (cookie affinity) |
| Database connection pooling | β (simple TCP proxy) | β (not HTTP) |
| Canary deployments (1% to new version) | β (random) | β (header-based routing) |
| Video streaming (large responses) | β (DSR for bandwidth) | β (LB bottleneck) |
Connection Pooling
Connection pooling reuses TCP connections instead of opening a new one for each request. This avoids the latency cost of TCP + TLS handshakes.
Why Pool Connections?
Real Numbers: Connection Setup Cost
- TCP handshake: 1.5 RTT
- TLS 1.3 handshake: 1 RTT (TLS 1.2: 2 RTT)
- Total setup: 2.5 RTT (TLS 1.3) or 3.5 RTT (TLS 1.2)
- Cross-region RTT: 50-200ms
- Setup cost without pooling: 125-700ms per request!
Pool Sizing Formulas
Pool size is a critical tuning parameter. Too small: requests wait for connections. Too large: memory waste, backend overload.
HikariCP Formula (Database Pools)
connections = ((core_count * 2) + effective_spindle_count)
For SSDs (effectively infinite spindles): connections = core_count * 2
Example: 8-core database server β pool size = 16 connections per app instance
HTTP Connection Pool Sizing
pool_size = (requests_per_second / requests_per_connection_per_second) * safety_factor
Example: 1000 RPS, each connection handles 100 req/sec (10ms per request) β 10 connections * 2 (safety) = 20
| Pool Size | Pros | Cons |
|---|---|---|
| Too Small (e.g., 1-2) | Low backend load | High latency (queueing), poor throughput |
| Optimal (10-50) | Low latency, good throughput, efficient resource use | Requires tuning based on workload |
| Too Large (100+) | No queueing | Memory waste, backend overload, connection thrashing |
HTTP Connection Pools
HTTP clients (curl, requests, httpx, OkHttp) maintain connection pools per destination host.
to api.example.com] C2[Connection 2
to api.example.com] C3[Connection 3
to api.example.com] C4[Connection 4
to db.example.com] end T1 -->|Request| P T2 -->|Request| P T3 -->|Request| P P -->|Checkout| C1 P -->|Checkout| C2 P -->|Checkout| C3 P -->|Checkout| C4 C1 -->|HTTP Request| S1[api.example.com] C2 -->|HTTP Request| S1 C3 -->|HTTP Request| S1 C4 -->|HTTP Request| S2[db.example.com] style P fill:#5bc0de
# Python httpx with connection pooling
import httpx
# Pool with 100 max connections, 10 per host
client = httpx.Client(
limits=httpx.Limits(max_connections=100, max_keepalive_connections=10)
)
# Reuses connections automatically
response1 = client.get("https://api.example.com/users")
response2 = client.get("https://api.example.com/posts") # Same connection!
Database Connection Pools
Database connections are expensive (authentication, session state). Pools are essential for databases.
Pool: 10 connections] A2[App Instance 2
Pool: 10 connections] A3[App Instance 3
Pool: 10 connections] end subgraph "Connection Pooler (PgBouncer)" PG[PgBouncer
Pool: 50 connections] end subgraph "Database" DB[(PostgreSQL
Max connections: 100)] end A1 -->|10 connections| PG A2 -->|10 connections| PG A3 -->|10 connections| PG PG -->|50 connections| DB Note1[3 app instances * 10 = 30 connections
PgBouncer pools to 50
Database only sees 50, not 30] style PG fill:#f0ad4e style DB fill:#5cb85c
| Tool | Database | Pooling Mode | Use Case |
|---|---|---|---|
| HikariCP | Any JDBC (MySQL, Postgres, Oracle) | Application-level (per JVM) | Java/Kotlin apps |
| PgBouncer | PostgreSQL | Session, transaction, or statement | Shared pool across many apps |
| ProxySQL | MySQL | Transaction-level | MySQL pooling + query routing |
| SQLAlchemy | Any (Python DB-API) | Application-level | Python apps |
Failure Mode: Connection Pool Exhaustion
Symptom: Requests timeout waiting for a connection from the pool.
Diagnosis: Pool metrics show all connections checked out, queue depth growing.
Causes: Slow queries (hold connections longer), insufficient pool size, connection leaks (not returned).
Fix: Increase pool size, optimize queries, fix connection leaks, add connection timeout.
Interview Signal
Discussing connection pooling depth: "We size our database connection pool based on the backend's core count, not the number of app instances. With 50 app instances and a pool of 10 each, we'd have 500 connections, overwhelming our 16-core Postgres. We use PgBouncer in transaction mode to multiplex 500 app connections down to 32 database connections."
Keep-Alive: HTTP vs TCP
Keep-alive has two meanings: HTTP keep-alive (connection reuse) and TCP keepalive (dead connection detection). They're often confused!
HTTP Keep-Alive (Connection Reuse)
HTTP/1.0 closed connections after each request. HTTP/1.1 defaults to persistent connections (Connection: keep-alive), allowing multiple requests per TCP connection.
Real Numbers: Keep-Alive Impact
- TCP handshake cost: 1.5 RTT (e.g., 15ms for 10ms RTT)
- Typical web page: 50-100 resources (HTML, CSS, JS, images)
- Without keep-alive: 50 * 15ms = 750ms just for handshakes
- With keep-alive: 1 * 15ms = 15ms
- Speedup: 50x reduction in handshake overhead
TCP Keepalive (Connection Liveness Probe)
TCP keepalive is a mechanism to detect dead connections. After a connection is idle for tcp_keepalive_time, the kernel sends a probe packet. If no response after multiple probes, the connection is closed.
| Aspect | HTTP Keep-Alive | TCP Keepalive |
|---|---|---|
| Layer | Application (L7) | Transport (L4) |
| Purpose | Reuse connection for multiple requests | Detect dead connections |
| Default | Enabled in HTTP/1.1 | Disabled by default (opt-in per socket) |
| Timeout | 5-120 seconds (server config) | 2 hours (Linux default, very long!) |
| Closes connection if | Idle timeout exceeded, max requests reached, client sends Connection: close | No response to keepalive probes |
| Use Case | Web page loading (many resources) | Long-lived connections (database, SSH, WebSocket) |
HTTP Keep-Alive Timeout Tuning
Servers close idle keep-alive connections after a timeout. Too short: clients must reconnect frequently. Too long: server holds many idle connections.
Real Numbers: Typical Keep-Alive Settings
- Nginx default: 75 seconds timeout, unlimited requests
- Apache default: 5 seconds timeout, 100 requests per connection
- AWS ALB: 60 seconds idle timeout
- CloudFlare: 90 seconds timeout
| Timeout Value | Pros | Cons |
|---|---|---|
| Short (5-10s) | Frees resources quickly, low memory | Clients reconnect often, negating keep-alive benefit |
| Medium (60-120s) | Good balance for web traffic | Standard choice |
| Long (300s+) | Minimize reconnections for chatty clients | Many idle connections consume memory/file descriptors |
| Very Long (hours) | Suitable for WebSocket, SSE | Must use TCP keepalive to detect dead connections |
Failure Mode: Keep-Alive Timeout Mismatch
Symptom: Clients get intermittent "connection reset" errors.
Cause: Server closes connection due to idle timeout, but client doesn't know and sends request on closed connection.
Fix: Ensure client connection pool idle timeout < server keep-alive timeout. Example: server timeout = 60s, client pool timeout = 50s.
Head-of-Line (HOL) Blocking
HOL blocking occurs when one slow/lost packet blocks all subsequent packets, even if they're independent. This manifests at multiple layers.
TCP Head-of-Line Blocking
TCP guarantees in-order delivery. If packet N is lost, packets N+1, N+2, ... are buffered (not delivered to application) until N is retransmitted.
Real Numbers: TCP HOL Blocking Impact
- Packet loss rate: 0.1-1% typical on Internet
- Retransmission timeout: 200-1000ms (RTO)
- HOL delay: All queued packets wait for retransmission RTT
- Example: 1% loss, 100ms RTT β 1 in 100 packets blocked for 100ms
HTTP/1.1 Head-of-Line Blocking
HTTP/1.1 sends requests serially over a connection. Request 2 cannot be sent until response 1 is received, even if they're independent.
even though it finished first
Why This Matters for System Design
Browsers work around HTTP/1.1 HOL blocking by opening 6-8 parallel connections per domain. This multiplies connection overhead and doesn't scale. HTTP/2 solves this with multiplexing.
HTTP/2 Stream Multiplexing (Solves L7 HOL, Not L4)
HTTP/2 multiplexes multiple streams over a single TCP connection. Each stream is independent, so one slow response doesn't block others.
However, HTTP/2 still suffers from TCP-level HOL blocking. If one TCP packet is lost, all streams on that connection wait for retransmission.
HTTP/3 & QUIC: True Multiplexing Without HOL Blocking
QUIC (HTTP/3) runs over UDP and implements per-stream reliability. Loss on one stream doesn't block others.
| Protocol | L7 HOL Blocking | L4 (TCP) HOL Blocking | Best For |
|---|---|---|---|
| HTTP/1.1 | Yes (serial requests) | Yes (TCP) | Simple, legacy compatibility |
| HTTP/2 over TCP | No (multiplexed streams) | Yes (TCP) | Low-latency networks, HTTP/3 not available |
| HTTP/3 over QUIC | No (multiplexed streams) | No (UDP, per-stream reliability) | High-latency, lossy networks (mobile, WiFi) |
Real Numbers: HTTP/3 Performance Gain
- 1% packet loss, HTTP/2: 20-30% throughput degradation (TCP HOL blocking)
- 1% packet loss, HTTP/3: 5-10% degradation (only affected streams wait)
- Benefit multiplier: 2-3x better throughput under loss with HTTP/3
- Mobile networks: 2-5% packet loss common β HTTP/3 significantly better
Interview Signal
Explaining HOL blocking depth: "We migrated our mobile app from HTTP/2 to HTTP/3 because mobile networks have 2-3% packet loss. With HTTP/2, one lost packet blocked all streams on that connection, causing tail latencies to spike. HTTP/3's per-stream retransmission means one slow image load doesn't block the API response."
Connection Management at Scale
At scale, connection limits become bottlenecks: file descriptors, ephemeral ports, connection tracking tables.
File Descriptor Limits
On Unix systems, each socket is a file descriptor. Processes have per-process and system-wide limits.
# Check per-process limit
ulimit -n
# Output: 1024 (default on many systems)
# Check system-wide limit
cat /proc/sys/fs/file-max
# Output: 1048576 (example)
# Increase per-process limit (requires root or capability)
ulimit -n 65536
# Permanent change: edit /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
Failure Mode: File Descriptor Exhaustion
Symptom: "Too many open files" error, cannot accept new connections.
Diagnosis: lsof -p PID | wc -l shows file descriptor count approaching limit.
Causes: Connection leaks (not closing sockets), insufficient ulimit, file handle leaks.
Fix: Increase ulimit, fix leaks, use connection pooling with max size.
Ephemeral Port Exhaustion
Clients use ephemeral ports (32768-60999 on Linux) for outbound connections. With TIME_WAIT, ports are held for 60s after close.
IP: 10.0.0.5] -->|Port 32768| S[Server
IP: 10.0.1.10:80] C -->|Port 32769| S C -->|Port 32770| S C -->|...| S C -->|Port 60999| S Note1[28,231 possible connections
If each lasts 60s TIME_WAIT
Max sustained: 470 conn/sec] style C fill:#d9534f
Real Numbers: Ephemeral Port Math
- Default range: 32768-60999 (28,232 ports)
- TIME_WAIT duration: 60 seconds
- Max sustained connections: 28,232 / 60 = ~470 conn/sec per client IP
- To sustain 10K conn/sec: Need 22 client IPs or connection reuse
| Solution | How It Works | Tradeoffs |
|---|---|---|
| Increase ephemeral port range | sysctl net.ipv4.ip_local_port_range="10000 65535" | 55K ports instead of 28K, but still limited |
| Enable tcp_tw_reuse | Reuse TIME_WAIT ports for outbound connections | Safe for clients, don't use on servers |
| Connection pooling / keep-alive | Reuse connections, avoid TIME_WAIT accumulation | Best solution, but requires client support |
| Multiple client IPs | Each IP has independent ephemeral port range | Requires additional IPs, complex routing |
Connection Tracking Tables (conntrack)
Linux stateful firewalls (iptables with connection tracking) maintain a table of all connections. This table has a maximum size.
# Check current conntrack entries
cat /proc/sys/net/netfilter/nf_conntrack_count
# Output: 12543
# Check max conntrack entries
cat /proc/sys/net/netfilter/nf_conntrack_max
# Output: 65536
# Increase limit (requires root)
sysctl -w net.netfilter.nf_conntrack_max=262144
Failure Mode: Conntrack Table Full
Symptom: New connections fail with "nf_conntrack: table full, dropping packet" in dmesg.
Diagnosis: cat /proc/sys/net/netfilter/nf_conntrack_count equals nf_conntrack_max.
Causes: High connection rate, long-lived connections, DDoS attack.
Fix: Increase nf_conntrack_max, reduce timeout values, disable conntrack if not needed.
TIME_WAIT Accumulation at Scale
High-traffic servers accumulate many TIME_WAIT connections. This is usually harmless (they consume minimal memory), but can be confusing.
# Count connections in each state
netstat -an | awk '{print $6}' | sort | uniq -c | sort -rn
# Output:
# 45123 TIME_WAIT
# 8765 ESTABLISHED
# 234 LISTEN
# 12 FIN_WAIT1
Why This Matters for System Design
Seeing 100K TIME_WAIT connections is normal for a busy web server. They consume ~4KB each (socket structure), so 100K = 400MB. Modern servers have plenty of memory. The real concern is ephemeral port exhaustion on clients or conntrack table size on firewalls.
Network Protocols for Microservices
Microservices use specialized protocols optimized for service-to-service communication: binary encoding, multiplexing, schema enforcement.
gRPC: HTTP/2-Based RPC
gRPC uses HTTP/2 for transport, Protocol Buffers for serialization, and supports streaming. It's designed for low-latency, high-throughput microservices.
| Feature | gRPC | REST/JSON over HTTP/1.1 |
|---|---|---|
| Encoding | Binary (Protobuf, ~3-10x smaller) | Text (JSON) |
| Schema | Enforced (Protobuf definitions) | Optional (OpenAPI/Swagger) |
| Streaming | Bidirectional, server, client streaming | Limited (chunked encoding, SSE) |
| Multiplexing | Yes (HTTP/2) | No (serial requests) |
| Latency | Lower (binary, less parsing) | Higher (text parsing, no multiplexing) |
| Browser Support | Limited (needs gRPC-Web proxy) | Native |
| Use Case | Microservice-to-microservice | Browser-to-server, public APIs |
Real Numbers: gRPC Performance
- Protobuf vs JSON size: 3-10x smaller (depends on schema)
- Serialization speed: Protobuf ~5-10x faster than JSON
- Latency improvement: 20-50% lower than REST/JSON (less data, faster parsing)
- Connection overhead: HTTP/2 multiplexing reduces connection count by 10-100x
Apache Thrift: Facebook's RPC Framework
Thrift is similar to gRPC: schema-defined, binary encoding, cross-language. Predates gRPC (2007 vs 2015), widely used at Facebook/Meta.
| Aspect | gRPC | Apache Thrift |
|---|---|---|
| Transport | HTTP/2 (fixed) | Pluggable (HTTP, TCP, framed) |
| Serialization | Protobuf | Binary, Compact, JSON protocols |
| Streaming | Bidirectional (HTTP/2) | Limited (transport dependent) |
| Ecosystem | Google, CNCF, growing | Meta, mature, stable |
Protocol Buffers Wire Format
Protobuf uses variable-length encoding and field tags for efficient binary serialization.
// Protobuf schema
message User {
int32 id = 1;
string name = 2;
repeated string emails = 3;
}
// Example: User{id: 123, name: "Alice", emails: ["a@example.com"]}
// Wire format (hex):
// 08 7B - field 1 (id), varint, value 123
// 12 05 41 6C 69 63 65 - field 2 (name), length-delimited, "Alice"
// 1A 0F 61 40 65 78 61 6D 70 6C 65 2E 63 6F 6D - field 3 (emails[0]), "a@example.com"
// Total: 29 bytes
// Equivalent JSON:
// {"id":123,"name":"Alice","emails":["a@example.com"]}
// Total: 52 bytes (1.8x larger)
Why This Matters for System Design
Protobuf's efficiency compounds at scale. If your microservices exchange 1 TB/day of data, switching from JSON to Protobuf saves ~500 GB of network traffic. At $0.01/GB for data transfer, that's $5/day = $1,825/year savings, plus reduced latency.
Service Mesh Networking
Service meshes (Istio, Linkerd, Consul Connect) add a networking layer between microservices, providing observability, security, and traffic control without changing application code.
Sidecar Proxy Model (Envoy)
Each service pod has a sidecar proxy (Envoy) that intercepts all inbound/outbound traffic. The proxy enforces policies, collects metrics, and handles retries.
localhost:8080] -->|Outbound| EA[Envoy Proxy
Sidecar] end subgraph "Service B Pod" EB[Envoy Proxy
Sidecar] -->|Inbound| B[Service B
localhost:8080] end EA -->|mTLS
Circuit breaking
Retries
Metrics| EB Note1[Application sees localhost
Envoy handles all networking complexity] style EA fill:#5bc0de style EB fill:#5bc0de
Why This Matters for System Design
Without a service mesh, every microservice must implement retries, timeouts, circuit breaking, metrics, and mTLS in application code. With a mesh, these are declarative config in the proxy. When you have 50 microservices in 5 languages, this is a massive win for consistency.
Traffic Routing in Service Mesh
Service mesh proxies route traffic based on rules: canary deployments, A/B tests, fault injection.
Circuit Breaking at Network Level
Envoy can circuit-break at the connection level: if backend has too many connections or errors, stop sending traffic.
| Circuit Breaker Config | Meaning | Typical Value |
|---|---|---|
| Max connections | Total connections to backend | 1024 |
| Max pending requests | Requests queued waiting for connection | 1024 |
| Max requests per connection | HTTP/2 streams per connection | 100 |
| Consecutive errors | Errors before opening circuit | 5 |
| Interval | Time window for error counting | 10s |
Mutual TLS (mTLS) Between Services
Service mesh enables zero-trust networking: every service-to-service call uses mTLS for authentication and encryption.
Traffic encrypted B->>A: Encrypted Response
Real Numbers: Service Mesh Overhead
- Latency added: 0.5-2ms per hop (sidecar proxy processing)
- CPU overhead: 5-15% per pod (Envoy proxy)
- Memory overhead: 50-200MB per pod (Envoy)
- Benefit: Consistent observability, security, resilience without code changes
Interview Signal
Discussing service mesh tradeoffs: "We adopted Istio because we had 30 microservices implementing retries inconsistently. Some had exponential backoff, others linear, some had no retries. Istio gave us a single place to configure retries, timeouts, and circuit breaking. The 1-2ms latency cost is worth it for operational simplicity."
Network Debugging
When systems fail, network issues are often the culprit. Knowing how to diagnose connection problems is essential.
tcpdump & Wireshark Patterns
tcpdump captures raw packets. Wireshark provides GUI analysis. Key patterns to recognize:
# Capture packets on interface eth0, port 80
tcpdump -i eth0 -n port 80
# Capture full packet content (not just headers)
tcpdump -i eth0 -n -A port 80
# Save to file for Wireshark analysis
tcpdump -i eth0 -w capture.pcap port 80
# Common patterns:
# 1. SYN retransmissions (server not responding)
# Client sends SYN, no SYN-ACK, client retries
12:00:00.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [S], seq 123
12:00:01.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [S], seq 123 # Retransmit!
12:00:03.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [S], seq 123 # Retransmit!
# 2. RST from server (connection refused)
12:00:00.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [S], seq 123
12:00:00.001 IP 10.0.0.5.80 > 1.2.3.4.5678: Flags [R], seq 0 # Reset!
# 3. Window size 0 (receiver buffer full, flow control)
12:00:00.000 IP 10.0.0.5.80 > 1.2.3.4.5678: Flags [.], ack 500, win 0
# Sender must wait for window update
# 4. Duplicate ACKs (packet loss, triggers fast retransmit)
12:00:00.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [.], ack 100
12:00:00.001 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [.], ack 100 # Duplicate
12:00:00.002 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [.], ack 100 # Duplicate
12:00:00.003 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [.], ack 100 # 3rd duplicate = retransmit
Network Partitions: How They Look
A network partition splits a cluster into two groups that cannot communicate. Each group may think the other is dead.
Leader] N2[Node 2] end subgraph "Datacenter B" N3[Node 3] N4[Node 4] end N1 -.->|Partition!
No connectivity| N3 N1 -.->|Partition!| N4 N2 -.->|Partition!| N3 N2 -.->|Partition!| N4 N1 --> N2 N3 --> N4 Note1[Datacenter A can't reach B
But A nodes can talk to each other
B nodes can talk to each other] style N1 fill:#d9534f style N3 fill:#d9534f
Failure Mode: Split-Brain from Network Partition
Symptom: Two leaders elected, conflicting writes, data inconsistency.
Cause: Network partition, no quorum enforcement.
Detection: Logs show "lost connection to N nodes", multiple nodes claiming to be leader.
Prevention: Use quorum-based consensus (Raft, Paxos), require majority for leader election.
Split-Brain Scenarios
Split-brain occurs when a cluster partitions and each side elects a leader, causing divergent state.
Two leaders, divergent state
| Mitigation Strategy | How It Works | Tradeoff |
|---|---|---|
| Quorum (majority) | Require N/2 + 1 nodes to elect leader | Can't make progress if majority partitioned |
| Fencing (STONITH) | Kill old leader if new one elected | Requires reliable kill mechanism |
| Lease-based leadership | Leader lease expires, step down if can't renew | Brief unavailability during lease expiry |
| Witness node | Odd-numbered tie-breaker in 3rd datacenter | Requires 3rd location |
Interview Signal
Explaining split-brain prevention: "Our database cluster uses Raft with 5 nodes across 3 datacenters. Even if one datacenter partitions (2 nodes lost), the remaining 3 form a quorum and continue serving writes. The partitioned nodes step down because they can't reach a majority. When the network heals, they rejoin as followers and replicate the leader's state."
Bandwidth vs Latency vs Throughput
These terms are often confused. Understanding the distinctions is critical for capacity planning.
Definitions
| Term | Definition | Unit | Analogy |
|---|---|---|---|
| Latency | Time for a packet to travel from source to destination | Milliseconds (ms) | Highway travel time (A to B) |
| Bandwidth | Maximum data transfer rate of a link | Bits per second (bps, Gbps) | Highway width (lanes) |
| Throughput | Actual data transfer rate achieved | Bits per second (bps, Gbps) | Cars actually passing per second |
Key Insight: Latency vs Bandwidth
You can have high bandwidth but high latency (e.g., satellite: 50 Mbps but 600ms latency). Or low bandwidth but low latency (e.g., LAN: 100 Mbps but 0.5ms latency). For interactive apps, latency matters more. For bulk transfers, bandwidth matters more.
Bandwidth-Delay Product (BDP)
BDP is the amount of data "in flight" on a network link. It determines optimal TCP buffer size.
Formula: Bandwidth-Delay Product
BDP = Bandwidth (bps) Γ Round-Trip Time (seconds)
Example: 1 Gbps bandwidth, 100ms RTT
BDP = 1,000,000,000 bps Γ 0.1 s = 100,000,000 bits = 12.5 MB
Meaning: To fully utilize the link, TCP must have 12.5 MB of unacknowledged data in flight.
RTT: 100ms] N --> R[Receiver] Note1[BDP = 1 Gbps Γ 100ms = 12.5 MB
TCP buffer must be >= 12.5 MB
to keep link full] style N fill:#5bc0de
| Scenario | Bandwidth | RTT | BDP | Required TCP Buffer |
|---|---|---|---|---|
| LAN | 1 Gbps | 1 ms | 125 KB | ~256 KB |
| Cross-region | 1 Gbps | 50 ms | 6.25 MB | ~8 MB |
| Intercontinental | 1 Gbps | 200 ms | 25 MB | ~32 MB |
| Satellite | 50 Mbps | 600 ms | 3.75 MB | ~4 MB |
Buffer Bloat: Too Much Buffering
Bufferbloat occurs when network devices have excessively large buffers, increasing latency under load.
Sending fast] -->|1 Gbps| R[Router
Large buffer: 1 second] R -->|100 Mbps link| D[Destination] Note1[Router buffer fills up
Packets wait 1 second in queue
Latency spikes from 10ms to 1000ms!] style R fill:#d9534f
Real Numbers: Buffer Bloat Impact
- Symptom: Latency spikes during bulk transfers (downloads, uploads)
- Cause: Router buffers sized for 100ms-1s of traffic
- Fix: Active Queue Management (AQM) algorithms like CoDel, fq_codel
- Result: Latency under load drops from 500ms to 20ms
Interview Signal
Discussing bandwidth-delay product: "For our CDN serving intercontinental traffic, we increased TCP buffer sizes from 4MB to 32MB. Our bandwidth is 10 Gbps with 200ms RTT to Asia, so BDP is 250 MB. Without large enough buffers, we couldn't fill the pipe, wasting bandwidth."
Data Center Networking
Modern datacenters use specialized topologies to provide high bandwidth, low latency, and fault tolerance for distributed systems.
Spine-Leaf Topology
Spine-leaf is the standard datacenter topology. Every leaf switch connects to every spine switch, providing equal-cost paths between any two servers.
20 servers] R2[Rack 2
20 servers] R3[Rack 3
20 servers] R4[Rack 4
20 servers] end L1 --> S1 L1 --> S2 L1 --> S3 L2 --> S1 L2 --> S2 L2 --> S3 L3 --> S1 L3 --> S2 L3 --> S3 L4 --> S1 L4 --> S2 L4 --> S3 L1 --> R1 L2 --> R2 L3 --> R3 L4 --> R4 style S1 fill:#2374ab style S2 fill:#2374ab style S3 fill:#2374ab style L1 fill:#5bc0de style L2 fill:#5bc0de style L3 fill:#5bc0de style L4 fill:#5bc0de
Why Spine-Leaf?
- Predictable latency: Any server to any server is exactly 2 hops (leaf β spine β leaf)
- High bandwidth: Multiple equal-cost paths (ECMP) for load balancing
- Scalability: Add more spines or leaves without topology changes
- Fault tolerance: Lose one spine, traffic reroutes to others
East-West vs North-South Traffic
Client β Server| Edge[Edge Routers] Edge --> Firewall[Firewall/LB] Firewall --> Web[Web Tier] Web -->|East-West
Server β Server| App[App Tier] App -->|East-West| DB[DB Tier] Note1[North-South: 20%
East-West: 80%] style Web fill:#5bc0de style App fill:#5bc0de style DB fill:#5bc0de
| Traffic Type | Direction | Typical % | Characteristics |
|---|---|---|---|
| North-South | Client β Datacenter | 20% | Internet-facing, high latency, variable bandwidth, requires security |
| East-West | Server β Server in datacenter | 80% | Microservices, low latency, high bandwidth, trusted network |
Why This Matters for System Design
Traditional 3-tier datacenter networks (core-aggregation-access) were designed for north-south traffic. Microservices shifted the majority to east-west. Spine-leaf topology optimizes for east-west with full bisection bandwidth: any leaf can send at full speed to any other leaf.
Equal-Cost Multi-Path (ECMP) Routing
ECMP load balances traffic across multiple equal-cost paths. In spine-leaf, a packet from Leaf 1 to Leaf 2 can go through Spine 1, 2, or 3.
Server A] -->|Flow 1| S1[Spine 1] L1 -->|Flow 2| S2[Spine 2] L1 -->|Flow 3| S3[Spine 3] S1 --> L2[Leaf 2
Server B] S2 --> L2 S3 --> L2 Note1[ECMP hashes packet 5-tuple
src IP, dst IP, src port, dst port, protocol
Same flow always uses same path] style S1 fill:#5cb85c style S2 fill:#5cb85c style S3 fill:#5cb85c
Real Numbers: ECMP Benefits
- Paths: 3-16 spines typical (3-16x bandwidth multiplication)
- Link utilization: 80-95% (vs 30-50% without ECMP)
- Failure recovery: Automatic (routing protocol detects spine failure, redistributes flows)
BGP for Anycast: Same IP, Multiple Locations
Anycast announces the same IP prefix from multiple locations. Routers send traffic to the nearest location based on BGP routing.
New York] -->|Route to 1.2.3.4
Closest: US-East| R1[Router] C2[Client
London] -->|Route to 1.2.3.4
Closest: EU-West| R2[Router] R1 --> DC1[Datacenter US-East
Announces 1.2.3.4] R2 --> DC2[Datacenter EU-West
Announces 1.2.3.4] DC3[Datacenter Asia
Announces 1.2.3.4] Note1[Same IP address 1.2.3.4
BGP routes client to nearest datacenter] style DC1 fill:#5cb85c style DC2 fill:#5cb85c style DC3 fill:#5cb85c
| Anycast Use Case | How It Works | Example |
|---|---|---|
| DNS | 8.8.8.8 routed to nearest Google DNS server | Google Public DNS, Cloudflare 1.1.1.1 |
| CDN | Edge PoPs announce same prefix, client routed to nearest | Cloudflare, Fastly edge network |
| DDoS Mitigation | Scrubbing centers announce victim IP during attack | Cloudflare Magic Transit |
| Load Balancing | Multiple servers announce same VIP, traffic distributed | L4 load balancers in multiple datacenters |
Interview Signal
Explaining anycast depth: "Our CDN uses anycast to route users to the nearest edge PoP. We announce our IP ranges from 100+ locations via BGP. A user in Tokyo connects to our Tokyo PoP, latency drops from 200ms (US) to 10ms. During a DDoS attack on Tokyo, we withdraw the BGP announcement, routing traffic to Osaka instead."
Interview Preparation: Networking Questions
Common FAANG interview questions and how to answer them with depth.
Question: Design a load balancer. L4 or L7?
Good Answer: "Depends on requirements. For a high-throughput, single-service backend (e.g., database proxy), I'd use L4 (TCP load balancing) for minimal latency and max throughput. If we need content-based routing (e.g., /api to service A, /static to CDN), then L7 (HTTP load balancing) is necessary despite the CPU cost. For very large responses (video streaming), I'd use L4 with DSR to avoid the LB becoming a bandwidth bottleneck."
Question: Your API has high latency. How do you investigate?
Good Answer: "First, add distributed tracing to measure time spent in each service hop. If latency is in network calls, check connection pooling: are we reusing connections or paying TCP+TLS handshake cost (3.5 RTT) per request? For cross-region calls, that's 150-200ms just for setup. Next, check if we're using HTTP/1.1 (serial requests) vs HTTP/2 (multiplexing). Use tcpdump to verify: look for repeated SYN packets (connection setup) vs reuse. Finally, measure BDP: if our TCP buffer is too small for high-latency links, we can't fill the pipe."
Question: How does HTTP/2 improve performance over HTTP/1.1?
Good Answer: "HTTP/2 adds stream multiplexing, header compression, and server push. Multiplexing eliminates HTTP/1.1 HOL blocking: multiple requests share one connection, so one slow response doesn't block others. This reduces connection count from 6-8 per domain to 1, saving handshake overhead. Header compression (HPACK) reduces header size by 85-90% for repeated headers. However, HTTP/2 still has TCP-level HOL blocking: if one TCP packet is lost, all streams wait for retransmission. HTTP/3 (QUIC) solves this with per-stream reliability over UDP."
Question: Your service is hitting connection limits. How do you scale?
Good Answer: "First, verify the bottleneck: file descriptors (ulimit -n), ephemeral ports (28K default range), or conntrack table. For file descriptors, increase ulimit. For ephemeral ports, enable tcp_tw_reuse (clients only) and use connection pooling to reuse connections instead of accumulating TIME_WAIT. If we're a client making many outbound connections, add more IP addresses (each has independent 64K port range). For connection tracking, increase nf_conntrack_max or disable conntrack if we're not doing stateful firewalling. Long-term, use connection pooling and HTTP keep-alive to minimize connection churn."
Question: Explain split-brain and how to prevent it.
Good Answer: "Split-brain occurs when a distributed system partitions into multiple groups, each electing a leader, causing divergent state. Example: 5-node cluster splits 2-3. Without quorum, both sides elect a leader and accept writes, creating conflicts. Prevention: require quorum (N/2 + 1) for leader election. In a 5-node cluster, need 3 votes. If network partitions 2-3, the 3-node side can elect a leader (has quorum), but the 2-node side cannot. Use consensus algorithms like Raft or Paxos which enforce quorum. Additionally, use lease-based leadership: leader must renew lease with majority; if it can't, it steps down, preventing dual leadership."
| Signal Phrase | What It Demonstrates |
|---|---|
| "TCP handshake is 1.5 RTT, TLS adds 1 RTT, so connection setup is 2.5 RTT" | Understands network latency breakdown |
| "We use PgBouncer in transaction mode to multiplex 500 app connections to 32 database connections" | Connection pooling at scale |
| "HTTP/2 solves L7 HOL but not L4; HTTP/3 solves both" | Deep protocol understanding |
| "BDP is 12 MB for 1 Gbps / 100ms RTT, so we increased TCP buffer to 16 MB" | Performance tuning depth |
| "We use spine-leaf topology for predictable 2-hop latency and ECMP for bandwidth" | Datacenter networking knowledge |
| "Our service mesh adds 1ms latency but gives us mTLS, circuit breaking, and observability" | Tradeoff awareness |
Final Interview Tip
Always quantify. Don't say "connection pooling improves performance." Say "Without pooling, each request pays 150ms (cross-region TCP+TLS handshake). With pooling, that's 0ms. For 1000 RPS, we save 150,000ms = 2.5 minutes of cumulative latency per second, a 25x improvement in P99 latency."
TCP Congestion Control Algorithms Deep Dive
TCP congestion control prevents overwhelming the network. Different algorithms use different strategies to detect and respond to congestion, with massive performance differences in high-bandwidth or lossy networks.
TCP Reno: The Classic Algorithm
TCP Reno (1990) is the foundational congestion control algorithm. It uses packet loss as the primary signal of congestion.
Four Phases of Reno
| Phase | Growth Type | Trigger | Action |
|---|---|---|---|
| Slow Start | Exponential | Connection start or timeout | Double cwnd every RTT (cwnd += 1 per ACK) Fast ramp-up until ssthresh |
| Congestion Avoidance | Linear (Additive Increase) | cwnd >= ssthresh | Increase cwnd by 1 MSS per RTT Cautious growth to probe capacity |
| Fast Retransmit | - | 3 duplicate ACKs | Retransmit missing packet immediately Don't wait for timeout (1s+) |
| Fast Recovery | Multiplicative Decrease | After fast retransmit | ssthresh = cwnd / 2 cwnd = ssthresh + 3 Stay in congestion avoidance (don't reset to slow start) |
cwnd doubles
every RTT] -->|cwnd >= ssthresh| B[Congestion
Avoidance
cwnd += 1/RTT] B -->|3 dup ACKs| C[Fast
Retransmit] C --> D[Fast Recovery
cwnd = ssthresh/2 + 3] D --> B B -->|Timeout| A A -->|Timeout| A
AIMD Sawtooth Pattern
Reno's Additive Increase / Multiplicative Decrease (AIMD) creates a characteristic sawtooth graph:
Problem: Reno treats all loss as congestion. In wireless/cellular networks with random loss (signal interference, not congestion), Reno unnecessarily reduces throughput.
CUBIC: High-BDP Optimization
CUBIC (2008) is the default algorithm in Linux since 2.6.19. It solves Reno's inefficiency in high-bandwidth delay product (BDP) networks.
Reno's Problem: Slow Recovery in High-BDP Networks
Example: 1 Gbps link, 100ms RTT β BDP = 12.5 MB. If cwnd was 10,000 packets before loss, Reno cuts to 5,000 packets, then adds 1 packet/RTT. To recover to 10,000 packets takes 5,000 RTTs = 500 seconds (8+ minutes!).
CUBIC's Solution: Cubic Function Growth
Instead of linear growth, CUBIC uses a cubic function centered around the last congestion point:
W(t) = C Γ (t - K)Β³ + Wmax
Where:
- W(t) = cwnd at time t after last congestion event
- C = scaling constant (0.4 by default)
- K = time to reach Wmax (when cubic function = 0)
- Wmax = cwnd when last congestion occurred
- t = elapsed time since last congestion
Growth Pattern
- Just after loss: Fast growth (concave phase) to quickly recover
- Near Wmax: Slow growth (inflection point) to cautiously probe if capacity increased
- Beyond Wmax: Moderate growth (convex phase) to find new capacity
to 9K in 50 RTTs] C --> D[Slow growth near Wmax
cautious probing] D --> E[Convex growth beyond Wmax
find new capacity]
Result: CUBIC recovers much faster than Reno in high-BDP networks (data center, long-distance fiber). In the 1 Gbps / 100ms RTT example, CUBIC recovers in seconds instead of 8 minutes.
RTT Fairness
CUBIC's growth is time-based (not RTT-based like Reno). This improves fairness between flows with different RTTs sharing the same bottleneck.
BBR (Bottleneck Bandwidth and RTT): The Modern Approach
BBR (2016) from Google represents a paradigm shift: don't use packet loss as a congestion signal.
Why Loss is a Bad Signal
- Random loss: Wireless networks have 1-5% loss from signal issues, not congestion
- Bufferbloat: Large router buffers hide congestion until buffers fill and loss occurs β high latency before correction
- Reactive: Loss-based algorithms wait for congestion to occur, then react
BBR's Approach: Model-Based Control
BBR builds a model of the network path by continuously measuring:
- BtlBw (Bottleneck Bandwidth): Maximum delivery rate observed in recent window (10 RTTs)
- RTprop (Round-Trip Propagation time): Minimum RTT observed in recent window (10s)
Key Insight: Optimal operating point is cwnd = BtlBw Γ RTprop (the BDP). At this point, the pipe is full but queues are not building.
Four BBR States
| State | Goal | Action | Duration |
|---|---|---|---|
| STARTUP | Find bottleneck bandwidth | Double sending rate each RTT (like slow start) Stop when delivery rate stops increasing (3 consecutive rounds) |
Initial connection (few RTTs) |
| DRAIN | Drain queue built during STARTUP | Send at BtlBw / 2.89 (geometric mean of 2x growth) Reduces cwnd to match actual BDP |
1 RTT |
| PROBE_BW | Check if bandwidth increased | Cycle pacing rate: 1.25Γ, 1.0Γ, 0.75Γ every 8 RTTs 98% of connection lifetime spent here |
Most of connection |
| PROBE_RTT | Check if RTprop decreased | If RTprop not updated in 10s, reduce cwnd to 4 packets for 200ms Allows queues to fully drain to measure true propagation delay |
200ms every 10s |
(delivery rate stops growing) DRAIN --> PROBE_BW: Queue drained PROBE_BW --> PROBE_BW: Cycle pacing
(1.25x, 1x, 0.75x) PROBE_BW --> PROBE_RTT: RTprop not updated
in 10 seconds PROBE_RTT --> PROBE_BW: After 200ms
BBR Performance
Real-world results:
- YouTube: 2-25x throughput improvement on mobile/satellite networks with 1-5% loss
- Google B4 network: 2x throughput increase in wide-area data center links
- Bufferbloat: Reduces latency by 10-100x by not filling buffers unnecessarily
Why BBR Handles Lossy Networks
Reno/CUBIC: 1% loss β reduce cwnd by 50% every 100 packets β throughput craters.
BBR: 1% loss β ignore it, maintain cwnd = BtlBw Γ RTprop β full throughput.
BBR only reduces rate if RTT increases (queue building) or delivery rate decreases (true congestion), not on random loss.
Algorithm Comparison
| Aspect | Reno | CUBIC | BBR |
|---|---|---|---|
| Congestion Signal | Packet loss | Packet loss | RTT increase + delivery rate |
| Growth | Linear (AIMD) | Cubic function | Model-based (BDP target) |
| Loss Sensitivity | High (50% cut per loss) | High (30% cut per loss) | Low (ignores random loss) |
| High-BDP Performance | Poor (slow recovery) | Good (fast recovery) | Excellent (model-driven) |
| Bufferbloat | Bad (fills buffers) | Bad (fills buffers) | Good (avoids filling buffers) |
| Fairness (RTT) | Unfair (favors short RTT) | Better (time-based growth) | Good (model-based) |
| Adoption | Legacy systems | Linux default (most servers) | Google services, YouTube, growing |
Interview Tip: BBR as the Solution to Mobile Performance
When asked "how does YouTube maintain quality on lossy mobile networks?" the answer is BBR. Reno/CUBIC interpret packet loss as congestion and reduce throughput, but mobile networks have 1-5% random loss from signal interference, not congestion. BBR ignores loss and instead monitors RTT (queue buildup) and delivery rate (true congestion), achieving 2-25x throughput improvement on lossy networks.
BBR Fairness Concerns
BBR can be aggressive toward loss-based algorithms when sharing a bottleneck. If a CUBIC flow backs off due to loss, BBR (which ignores loss) claims that bandwidth. This is being addressed in BBRv2 (2020) with enhanced fairness mechanisms.
QUIC vs TCP HOL Blocking β Detailed
Head-of-line (HOL) blocking is a critical performance issue where one slow/lost item blocks unrelated items behind it. QUIC solves TCP's HOL blocking problem, which is why HTTP/3 over QUIC significantly outperforms HTTP/2 over TCP on lossy networks.
TCP HOL Blocking: The Problem
TCP guarantees in-order delivery within a single connection. If packet 5 is lost, packets 6-10 (even if received) are held in the TCP receive buffer and not delivered to the application until packet 5 is retransmitted and arrives.
HTTP/2 + TCP: Multiplexed Streams, Single TCP Connection
HTTP/2 multiplexes multiple HTTP requests (streams) over a single TCP connection. This reduces connection overhead (no repeated handshakes) but inherits TCP's HOL blocking:
Stream B packet 1 RECEIVED
Stream C packet 1 RECEIVED
BUT BLOCKED by lost Stream B packet 2 Note over TCP: TCP waits for Stream B packet 2 retransmission Note over TCP: ALL STREAMS BLOCKED
even though Stream A and C data arrived Server->>TCP: Stream B packet 2 retransmitted (1 RTT later) TCP->>Browser: Stream A packet 3 β (finally delivered) TCP->>Browser: Stream B packet 1 β TCP->>Browser: Stream B packet 2 β TCP->>Browser: Stream C packet 1 β
Key Issue: A single lost packet (Stream B packet 2) blocks ALL streams, even though Stream A packet 3 and Stream C packet 1 arrived successfully. This is TCP-level HOL blocking.
QUIC Solution: Independent Streams
QUIC is a transport protocol built on UDP (to avoid kernel TCP stack limitations). Each QUIC stream has independent flow control and delivery. A lost packet only blocks ITS stream.
HTTP/3 + QUIC: Same Scenario, No Blocking
Streams A and C continue independently Server->>QUIC: Stream B packet 2 retransmitted (1 RTT later) QUIC->>Browser: Stream B packet 1 β QUIC->>Browser: Stream B packet 2 β
Result: Stream A and Stream C complete immediately. Only Stream B waits for its lost packet. No cross-stream blocking.
Real-World Impact
| Network Condition | HTTP/2 (TCP) | HTTP/3 (QUIC) | Improvement |
|---|---|---|---|
| 1% packet loss | Page load: 5.2s (frequent HOL blocking) |
Page load: 4.5s (only affected streams block) |
15% faster |
| 5% packet loss | Page load: 12.8s (severe HOL blocking) |
Page load: 6.1s (streams mostly independent) |
52% faster |
| Mobile (3G, ~2% loss) | Video rebuffer rate: 18% | Video rebuffer rate: 9% | 50% reduction |
| Low-latency network (0.1% loss) |
Minimal difference | Minimal difference | ~5% faster (handshake savings) |
Source: Google measured these results deploying HTTP/3 to YouTube and Chrome.
Why QUIC Needed to Be Built on UDP
TCP is implemented in the operating system kernel. Changing TCP behavior (e.g., per-stream delivery) requires OS updates, which take years to deploy (Windows, macOS, Linux, mobile, IoT). UDP is a thin wrapper around IP packetsβapplications control all behavior. QUIC implements reliability, congestion control, and multiplexing in userspace (browser/app), allowing rapid iteration without OS changes.
Additional QUIC Benefits
- 0-RTT Reconnection: Cached connection info allows sending data in first packet (no handshake on reconnect)
- Connection Migration: Switch from WiFi to cellular without breaking connection (identifies connection by ID, not 4-tuple)
- Improved Loss Recovery: Better ACK system (per-packet sequence numbers vs TCP's byte-stream sequence numbers)
- Encrypted by Default: TLS 1.3 integrated into QUIC handshake (not optional)
Interview Tip: TCP vs QUIC HOL Blocking
When asked "Why is HTTP/3 faster than HTTP/2 on mobile?", focus on HOL blocking. TCP's in-order delivery means one lost packet blocks all multiplexed streams, even those whose data arrived. QUIC uses independent streamsβonly the affected stream blocks. On a 1% loss network, this results in 15% faster page loads. Emphasize this is most impactful on lossy networks (mobile, WiFi) and less significant on stable datacenter links.
HTTP/1.1 vs HTTP/2 vs HTTP/3 HOL Comparison
| Protocol | Connections | Multiplexing | HOL Blocking |
|---|---|---|---|
| HTTP/1.1 | 6 parallel TCP connections | No (1 request per connection at a time) | Application-level: One slow response blocks subsequent requests on that connection (Mitigated by 6 connections) |
| HTTP/2 + TCP | 1 TCP connection | Yes (multiple streams) | Transport-level: Lost TCP packet blocks all streams (Worse than HTTP/1.1 on lossy networks!) |
| HTTP/3 + QUIC | 1 QUIC connection | Yes (multiple streams) | No HOL blocking: Lost packet only blocks its stream |
The HTTP/2 Paradox
HTTP/2 can be slower than HTTP/1.1 on lossy networks. HTTP/1.1 uses 6 parallel TCP connections. If one connection loses a packet, only 1/6 of traffic is blocked. HTTP/2 uses 1 TCP connection. If it loses a packet, 100% of traffic is blocked. This is why HTTP/3 was necessaryβHTTP/2's multiplexing is powerful but crippled by TCP's HOL blocking.