Frontend Labs
⋯

Part 7 — Layer 5: Session

Job

Establish, maintain, synchronise, resume, and end a dialogue between two applications: something that lasts longer than a single packet or single request.

The honest truth

TCP/IP has no dedicated session layer protocol. Session responsibilities are spread across TLS, HTTP, and your application. That's exactly why session problems are so often nobody's problem until they become an incident.

In our story

Five different "sessions" are active at once when you click Place order:

"Session" Lifetime What establishes it What it buys you
TCP connection Seconds to minutes 3-way handshake Reliable byte stream (strictly L4, but the browser treats it as a reusable session)
TLS session Hours (via tickets) TLS handshake; resumption with session tickets / PSK Skip the expensive handshake work next time
HTTP/2 connection + streams Minutes ALPN negotiation (h2), then SETTINGS frames Many concurrent requests (streams) multiplexed over one TCP+TLS connection
Browser connection pool Until idle timeout Browser policy Your click reuses the connection opened when the cart page loaded, so no new TCP or TLS handshake is needed
Application login session Days Login → Set-Cookie: session=… or a JWT The server knows who you are on every stateless HTTP request

So in the common case, your click skips the TCP and TLS handshakes entirely: the browser already has a warm HTTP/2 connection to shop.example.com from loading the checkout page. The POST becomes a new stream (stream ID 15, say) on that existing connection.

Two more session-level behaviours happen on the server side:

  • The ALB keeps its own pool of keep-alive connections to the pods, which is a separate session layer behind the proxy.
  • If the ALB has sticky sessions enabled, a cookie (AWSALB=…) pins you to one pod.

How it works

1. Session resumption (TLS). After a full handshake the server gives the client an encrypted session ticket. On reconnecting, the client presents it and both sides derive keys without repeating the certificate exchange. TLS 1.3 even allows 0-RTT data, where the request goes in the very first flight, but 0-RTT data can be replayed by an attacker, so it's only safe for idempotent requests (GET, never POST /orders).

2. Multiplexing (HTTP/2). One connection carries many independent streams, each with its own ID and flow control. That removes HTTP/1.1's "one request at a time per connection" limit. The catch is TCP-level head-of-line blocking: one lost TCP packet stalls all streams until it's retransmitted. HTTP/3 (QUIC over UDP) fixes this by doing loss recovery per stream.

3. Dialogue control and checkpointing. These are classic OSI session functions. Modern equivalents:

  • Resumable uploads (tus protocol, S3 multipart): restart from the last checkpoint after a disconnect.
  • WebSocket / gRPC streams: long-lived, full-duplex dialogues with their own keepalive pings.
  • Database sessions: Postgres connection state (transactions, SET variables, prepared statements) is session state. That's why transaction-mode poolers like PgBouncer break SET and session-level prepared statements.

4. Application sessions: stateful vs stateless.

Approach Where state lives Trade-off
Server-side session (cookie holds an ID → Redis) Shared store Easy revocation; extra lookup per request; the store is a dependency
Sticky sessions (cookie → specific instance) Instance memory Simple; breaks on deploys/scale-in, uneven load
Token (JWT holds the claims) The client No lookup; hard to revoke; token size added to every request

Try it yourself

# See ALPN (h2 vs http/1.1), TLS version, and whether the TLS session was reused
curl -sv -o /dev/null https://shop.example.com/ 2>&1 | grep -E 'ALPN|SSL connection|HTTP/'

# Two requests in one curl invocation: watch the connection get reused
curl -sv -o /dev/null -o /dev/null https://shop.example.com/ https://shop.example.com/health 2>&1 \
  | grep -E 'Connected to|Re-using|left intact'

# TLS session resumption test (look for "Reused" on later connections)
openssl s_client -connect shop.example.com:443 -servername shop.example.com -reconnect </dev/null 2>/dev/null \
  | grep -E '^(New|Reused)'

# Browser: chrome://net-internals/#sockets  and  DevTools → Network → "Connection ID" column

What breaks

Symptom Session-level cause
Sporadic 502s from the load balancer, especially after idle periods Keep-alive timeout mismatch: the backend closes an idle connection the LB still thinks is open (see war story)
Users logged out during a deploy Sessions stored in instance memory; sticky sessions pointed at terminated instances
One pod at 90% CPU, others idle Sticky sessions or long-lived HTTP/2 / gRPC connections pinning traffic
WebSocket drops every ~60 s An idle timeout somewhere on the path (LB, NAT, corporate proxy) is shorter than your ping interval
"Prepared statement does not exist" errors Transaction-mode connection pooler + session-level DB features

War story: "The 502s nobody could reproduce"

A Node.js service behind an ALB returned about 0.05% 502s with no errors in its own logs. Cause: the ALB's idle timeout was 60 s, but Node's server.keepAliveTimeout defaulted to 5 s. After 5 s of idleness, Node closed the backend connection. Sometimes the ALB had just sent a new request on that same connection at that exact moment. The request hit a closing socket, and the ALB returned 502. Fix: make the server's keep-alive timeout longer than the load balancer's idle timeout (for example 65 s vs 60 s), so the LB always closes first. Lesson (and a good interview question): in any proxy chain, the side that sends requests on a reused connection must be the one that closes idle connections first, so every hop's idle timeout must be shorter than the timeout of the hop behind it.

Why it matters to you

  • Connection reuse is the biggest session-layer performance lever. A warm connection avoids 2 RTTs (TCP + TLS) and TCP slow start.
  • Timeouts along a proxy chain are a design artefact. Document them per hop (client → CDN → LB → sidecar → app → DB).
  • Prefer stateless app servers with an external session store or tokens. Sticky sessions are technical debt that shows up at deploy time.
  • Long-lived connections defeat load balancing. gRPC/HTTP/2 clients on an L4 balancer send everything to one backend. See Part 15.

Go deeper in the series


Generated from real-life-example-osi.md · 8 Oct 2026 16:36