CMD Guide
HomeSystem DesignMicroservices Patterns

Performance Implications

An API Gateway sits on every request's critical path, so every microsecond it spends terminating TLS, verifying a token, matching a route, and re-opening a backend connection is added to every client call — the whole performance game is keeping that per-hop tax small and bounded while fanning out to backends in parallel so aggregation costs the slowest branch, not the sum.

The original catalog of this page named the right techniques (token bucket, leaky bucket, service mesh load balancing) but never said how any of them work, and answered "latency" with "optimize the code." Below, each mechanism is traced with real numbers so you can reason about the actual cost.

The extra hop, in real milliseconds

The gateway's headline cost is one more network hop. But be honest about which hop: a remote client already pays a wide-area round trip to reach anything in your region, so the gateway does not add that. What it adds is (a) one intra-datacenter hop from gateway to backend (~0.3–0.8 ms) plus (b) the gateway's own CPU work — route match, JWT signature verify, header rewrite (~1–3 ms warm). On the happy path that is a 2–4 ms tax. The danger is the tail: a connection-pool miss forces a fresh TCP+TLS handshake to the backend (adds 2 RTT ≈ 1–2 ms intra-DC, tens of ms if the pool is exhausted and the request queues), and a GC pause or CPU saturation on the gateway itself turns p99 into 50–200 ms.

Latency budget: one aggregated mobile request

A mobile home screen needs profile, recent orders, and recommendations. The gateway fans out to three services. Compare doing it sequentially versus scatter-gather (all three in flight at once):

SegmentCostSequential totalScatter-gather total
Client → Gateway (WAN RTT, paid either way)30 ms3030
Gateway CPU: TLS resume + JWT verify + route2 ms3232
Profile service (intra-DC hop + work)40 ms72
Orders service60 ms132
Recommendations service50 ms182
All three in parallel = max(40,60,50)60 ms92
Gateway merge + compress response3 ms18595
Client-observed latency185 ms95 ms

Same three calls; parallel fan-out nearly halves observed latency because the client waits for the slowest branch (60 ms) instead of the sum (150 ms). "Optimize the aggregation logic" concretely means: issue the backend calls concurrently, set a per-branch deadline (e.g. 80 ms) so one slow service cannot stall the whole page, and return partial results for non-critical branches like recommendations.

diagram
diagram

Rate limiting: token bucket, mechanically

A token bucket is a counter that refills at a fixed rate and caps at a maximum. The bucket holds up to C tokens and gains r tokens per second; each request removes one token; a request that finds the bucket empty is rejected (HTTP 429). Because a full bucket lets a backlog drain instantly, the pattern permits short bursts up to C while still enforcing a sustained ceiling of r requests/second — which is exactly what real traffic (spiky, then quiet) needs.

Traced example — C = 10, r = 5 tokens/sec (1 token every 200 ms)

The bucket starts full. A client fires a burst of 15 requests at t = 0, then 2 more at t = 0.4 s.

TimeTokens beforeEventDecisionTokens after
0 ms10reqs #1–#10 (burst)allow all 100
0 ms0reqs #11–#15reject 429 ×50
200 ms+1 refilled → 11
400 ms+1 refilled → 2reqs #16, #17allow both0

The burst of 10 sailed through (that is the point — occasional bursts are legitimate); the 11th–15th were shed instantly and cheaply; and by 400 ms the steady 5/sec refill had earned back exactly 2 tokens for the next 2 requests. A leaky bucket is the dual: requests enter a fixed-size queue and leave at a constant rate r; it smooths output to a perfectly even stream (good for protecting a fragile downstream) but cannot pass a burst — the 10 simultaneous requests would be spaced 200 ms apart instead of served at once. Choose token bucket to allow bursts, leaky bucket to eliminate them.

diagram
diagram

Distributed rate limiting and load balancing — the hard parts

Rate limiting across a gateway fleet. With 5 gateway replicas each keeping a local bucket, a global limit of 100 req/s becomes 5×20 — but skewed hashing sends 60% of a user's traffic to one replica and it throttles at 20 while the global count is only 60. The real fix is a shared counter: each replica does an atomic INCR with a TTL against Redis (a sliding-window or a centrally-held token bucket). This costs one ~0.3 ms Redis round trip per request and makes Redis a dependency on the hot path — so production systems use a two-tier scheme: enforce a generous local bucket to shed obvious floods with zero network cost, and reconcile against the shared counter for the true global limit.

Load balancing and the service mesh. A gateway doing central round-robin is blind to backend health between health-check intervals, so it keeps sending requests to a pod that is GC-pausing. A service mesh (e.g. Envoy sidecars) moves the decision to a proxy next to each caller and uses health-aware algorithms — least-request (send to the backend with the fewest in-flight calls) or EWMA latency weighting — plus outlier detection that ejects a slow pod after N consecutive 5xx. "More granular" concretely means the balancing decision is made per-call with live in-flight and latency data, not per-interval from a central table.

Sizing backend connection pools

A gateway keeps a warm pool of keep-alive connections to each backend. If the pool is too small, requests queue for a connection and tail latency spikes; if too large, you waste file descriptors and memory and can overwhelm the backend with idle sockets. A safe size is derived from the maximum concurrent in-flight requests you expect to that backend, not from total gateway traffic.

Worked example: gateway fleet = 5 replicas; target backend = Orders service; peak = 10,000 req/s; Orders mean latency ≈ 30 ms, p99 latency = 60 ms. Little's Law uses the mean sojourn time: λ × W = 10,000 × 0.030 = 300 concurrent requests on average. Sizing a connection pool to the tail is safer, so we use the p99 as a hedge: 10,000 × 0.060 = 600 concurrent requests. With 5 replicas, each gateway needs up to 600 / 5 = 120 concurrent connections. Add headroom for bursts (×1.5) → 180 connections per replica. Set pool max to 180, alarm on pool-wait time, and cap total connections so Orders is not drowned by idle keep-alives.

If the backend is shared by multiple callers, its total connection count is the sum across all gateway replicas and other clients. A backend that accepts at most 1,000 connections can be exhausted by 6 gateways × 180 = 1,080, so the limit must be negotiated with the backend team or enforced by the service mesh.

Source grounding: Little's Law derivation and connection-pool sizing are standard in queueing-theory references (Harchol-Balter, Performance Modeling and Design of Computer Systems) and in the Envoy proxy connection-pool documentation.

Pitfalls

When to use a gateway — and when not to

Reach for an API Gateway when you have many services behind one public edge and need cross-cutting concerns — TLS termination, auth, rate limiting, request shaping — applied uniformly at the boundary, and clients (mobile, third-party) that benefit from a single stable endpoint and response aggregation. Concrete signals: you are re-implementing JWT verification in every service; a client makes 6 calls to render one screen; you need one place to enforce quotas.

ApproachYou gainIt costsChoose it when
API GatewayOne edge for auth/limits/aggregation; clients simplifiedAn extra hop, a shared bottleneck + SPOF to make HA, an org chokepointNorth-south (client→system) traffic with heavy cross-cutting concerns
Direct client→serviceLowest latency, no shared componentEvery service re-implements auth/TLS/limits; clients tightly coupled to topologyInternal, trusted callers; few services; latency is sacred
Service mesh (sidecar)Health-aware LB, mTLS, retries for east-west traffic, no central hopA proxy per pod (memory/CPU), control-plane ops burdenService-to-service concerns dominate; you also want per-call LB
BFF (per-client gateway)Tailored aggregation per client type; teams own their edgeMore edges to run; logic duplicated across BFFsDivergent client needs (mobile vs web) strain a shared gateway

Choose THIS (a shared gateway) when north-south cross-cutting concerns and client simplification dominate. Prefer a service mesh when the pain is service-to-service resilience and load balancing. Prefer direct calls when callers are internal, trusted, and latency-critical. Prefer a BFF when one gateway is being pulled in incompatible directions by different clients. These compose: a gateway for north-south plus a mesh for east-west is the common large-system layout.

Takeaways


Re-authored and deepened for this guide. Draws on Sam Newman, Building Microservices (2nd ed.) on gateways, BFFs, and aggregation; Chris Richardson, Microservices Patterns (API Gateway chapter); the Envoy Proxy docs on least-request/EWMA load balancing and outlier detection; the classic token/leaky bucket definitions from the ATM traffic-shaping literature (Tanenbaum, Computer Networks); and Stripe's and Cloudflare's engineering writeups on distributed rate limiting with shared counters.

🤖 Don't fully get this? Learn it with Claude

Stuck on Performance Implications? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Performance Implications** (System Design) and want to truly understand it. Explain Performance Implications from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Performance Implications** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Performance Implications** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Performance Implications** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes