System Design (High-Level Design) Module 19 – Building blocks: traffic, storage and messaging
Design an API gateway
Design an API gateway fleet with route matching, a filter chain per route, token checks, rate limits across instances and configuration you can roll back.
What you will learn
- Estimate the instances, token-check CPU and log volume of an API gateway fleet
- Design a filter chain per route for authentication, limits, validation and routing
- Implement route matching with path normalisation and match precedence
- Limit requests and quotas across many instances, and compute the error of each method
- Release configuration in stages, with validation and automatic rollback
Before you start
What you’ll learn · Before you start
What you will learn
- Estimate the instances, token-check CPU and log volume of an API gateway fleet
- Design a filter chain per route for authentication, limits, validation and routing
- Implement route matching with path normalisation and match precedence
- Limit requests and quotas across many instances, and compute the error of each method
- Release configuration in stages, with validation and automatic rollback
Before you start
On this page
Every request to a company’s public API, 60,000 a second at peak across 900 routes, must have its caller checked, its limit counted and its path matched before any of 120 services sees it. An API gateway is the single front door that does this: a fleet of identical, stateless reverse proxy: A server that clients reach as if it were the site itself. It passes each request to one of the servers behind it, often ending TLS, routing by path, balancing load and caching on the way. More in “Forward proxies, reverse proxies and gateways” that run every request through the filter chain of the route it matches (TLS, authentication, rate limits and quotas, request validation, routing to a service, response shaping and logging), so that services do not each implement them.
Two things make it a design problem rather than a product to install. Every request pays for the chain, so the gateway must stay thin and fast; and the route table and its policies are configuration that changes many times a day, so a bad change must never reach every instance at once.
Requirements and estimate: the loss of a zone sizes the fleet
The company’s 120 services are used by its mobile apps, its website and about 2,000 partners. Today every team runs its own token checks and limits, and they disagree. Ask first:
| Question | Assumed here |
|---|---|
| How many requests a second at peak, how large, and across how many routes? | 60,000 a second at peak across 900 routes |
| Who calls: users with tokens, partners with API keys, or both? | User tokens from an identity provider, and partner API keys |
| Which limits: per user, per partner, per route or per plan, and how exact must they be? | Per-partner quotas and per-route limits, accurate to a few per cent |
| How much latency may the gateway add, and what must it never do when a dependency fails? | A few milliseconds at p99 at most, and no outage when a dependency fails |
| How often do routes and policies change, and who changes them? | About 200 configuration changes a day from 30 teams |
Functional requirements: route by host, path, method and headers; check tokens and API keys; rate limits and quotas; request size limits and schema validation; per-route timeouts and retries; access logs and traces. Non-functional requirements: a few milliseconds of overhead; no outage when a zone or any dependency fails; configuration changes without restarts; and a bad change contained to a small share of the traffic.
The first estimate, to change and run again:
# The first estimate for an API gateway fleet. Every input is an assumption, written down so that you can change it
# and run the script again.
import math
PEAK = 60_000 # requests a second at peak, every API together
PER_INSTANCE = 4_000 # requests a second one instance handles at its target CPU (TLS, filters, proxying)
ZONES = 3 # instances spread evenly over 3 zones; the fleet must survive the loss of one
JWT_VERIFY_MS = 0.1 # CPU time to check one token's signature
CACHE_HIT = 0.9 # share of requests whose token this instance verified a moment ago
SYNC_MS = 100 # each instance reports its rate-limit counts this often, in one batch
LOG_BYTES = 1_000 # one access-log record
ROUTES, ROUTE_BYTES = 900, 2_000
load = math.ceil(PEAK / PER_INSTANCE)
zone_safe = math.ceil(load / ((ZONES - 1) / ZONES))
fleet = math.ceil(zone_safe / ZONES) * ZONES
print("Instances")
print(f" for the load: {PEAK:,} / {PER_INSTANCE:,} = {load}")
print(f" to survive losing 1 of {ZONES} zones: {load} / ({ZONES - 1}/{ZONES}) = {load * ZONES / (ZONES - 1):.1f}, "
f"rounded up to {zone_safe}, and to {fleet} for the same number in every zone ({fleet // ZONES})")
print("Rate-limit decisions")
print(f" a central counter asked on every request: {PEAK:,} calls a second, each a network round trip")
print(f" local token buckets, counts sent every {SYNC_MS} ms in one batch: {fleet} x {1000 // SYNC_MS} "
f"= {fleet * 1000 // SYNC_MS} messages a second")
print("Token checks")
print(f" every request verified: {PEAK * JWT_VERIFY_MS / 1000:.1f} CPU cores")
print(f" with {CACHE_HIT:.0%} of tokens verified a moment ago: {PEAK * (1 - CACHE_HIT) * JWT_VERIFY_MS / 1000:.1f} cores")
print("Logs and configuration")
print(f" access logs: {PEAK * LOG_BYTES / 1e6:.0f} MB a second, {PEAK * LOG_BYTES * 86400 / 1e12:.2f} TB a day")
print(f" route table: {ROUTES} routes x {ROUTE_BYTES:,} bytes = {ROUTES * ROUTE_BYTES / 1e6:.1f} MB per version, "
f"sent to {fleet} instances") Output
Instances for the load: 60,000 / 4,000 = 15 to survive losing 1 of 3 zones: 15 / (2/3) = 22.5, rounded up to 23, and to 24 for the same number in every zone (8) Rate-limit decisions a central counter asked on every request: 60,000 calls a second, each a network round trip local token buckets, counts sent every 100 ms in one batch: 24 x 10 = 240 messages a second Token checks every request verified: 6.0 CPU cores with 90% of tokens verified a moment ago: 0.6 cores Logs and configuration access logs: 60 MB a second, 5.18 TB a day route table: 900 routes x 2,000 bytes = 1.8 MB per version, sent to 24 instances
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 gateway_estimate.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Three numbers decide the design. The fleet is sized for the loss of a zone, not for the load. A central counter on every request is 60,000 calls a second, while batched local counts are 240 messages. And caching recently verified tokens saves nine signature checks in ten.
Checkpoint
Check the estimate
The first design: a filter chain per route, cheapest refusals first
The first design of the API gateway
Text description of the diagram
The diagram shows the first design from top to bottom.
- Clients, such as mobile apps, the web app and partners, connect over HTTPS to a load balancer, which spreads their connections over the gateway instances.
- The gateway instances are stateless, from instance 1 to instance 24. Each holds route table v41 and runs the filter chain of the route a request matches.
- A dashed arrow shows the control plane, which publishes route tables as versioned snapshots that it validates, stages and can roll back.
- The instances proxy each request to a service, such as orders or payouts.
- Another dashed arrow shows the instances sending batched counts to a rate-limit service, which keeps the global counts.
Each part of the design has one job:
- Route table: maps host, path, method and headers to a route, with its backend, timeouts and filters.
- Filter chain: the ordered steps a route’s requests pass through, cheapest refusals first.
- Authentication filter: checks a token’s signature and claims, or an API key, before anything uses the identity.
- Rate limits and quotas: A rate limiter that holds up to a set number of tokens and refills them at the allowed rate: each request takes one token, and a request that finds the bucket empty is refused or made to wait. per key and route, enforced locally and shared through a limit service.
- Request validation: size limits and the route’s schema, so malformed requests never reach a service.
- Control plane: stores routes and policies as versioned snapshots, validates them and rolls them out in stages.
- Backend for frontend: a client team’s own service for aggregation and shaping, outside the shared gateway.
Follow one partner request through its route’s filters:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- The request arrives. A partner's request for /v2/payouts reaches instance 7 of the 24, which the load balancer picked. The instance has matched it to its route, and the route's filters run in order.
- TLS and the size limit. The instance ends TLS and checks the request's size first. Both are cheap, and an oversized request is refused before any other filter spends work on it.
- Who is calling. The authentication filter checks the API key and finds the partner it belongs to. Nothing later may use an identity that this filter has not proven.
- The partner's limit. The rate limit is counted for the proven partner, in a token bucket on this instance, so the request takes one token without a call to another service.
- The route's schema. The request is checked against the route's schema, so a malformed request never reaches the service.
- To the service. The instance sends the request to the payouts backend with the route's timeout and waits for the answer.
- Logged last. The access log is written last, when the outcome is known, with the partner, the route and the status.
- Two early refusals. A request whose key is unknown stops at the second filter with 401, and a partner past its limit stops at the third with 429. Each refusal is logged, and neither reaches the service.
The deep dives that decide whether the design works:
- How is a request matched to a route, quickly and without being fooled by its path?
- In what order do the filters run, and what does each do when its dependency fails?
- How do limits stay accurate across 24 instances without a call on every request?
- How does a configuration change reach the fleet without putting all of it at risk?
- How do the issuer’s signing keys rotate without an outage?
- What stays out of the gateway?
An answer is judged on requirements and estimates, routing, the filter chain, limits, configuration safety, failure handling and operations.
Checkpoint
Check the first design
The API, and route tables swapped whole Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Normalise the path once, then match by precedence
- Authentication fails closed, every other filter fails open
- Limits across a fleet: local buckets with batched counts
- A bad configuration push, reproduced and contained
- A thin gateway: client logic in backends for frontends
- At 10× handshakes and token checks dominate, at 100× the configuration
- Five alternatives, and why each loses
- What each level is expected to cover
- A sample answer and a 45-minute self-mock
- Practise: pick the route
- Pick the route for a request, by match precedence
- Interview questions
- When does logic belong in a backend for frontend rather than in the shared API gateway?
- A routing change containing one malformed route was pushed to every gateway instance at once, and all of them began to crash and restart. The whole API was down for ten minutes, although the route carried 0.5 % of the traffic. Design the configuration pipeline and the instances so that this cannot take the API down.
- Partners are allowed 1,000 requests a minute each, and 30 gateway instances enforce it by giving each instance a thirtieth of the limit. Some partners get only a fraction of their limit, while others exceed it after a scale-up. Explain both, and design a limiter that stays within a few per cent.
- A security review finds that a request for /public/..%2Fadmin/users reaches the admin API without a token through your gateway, although /admin requires one. Explain how, and fix it in the gateway and behind it.
- Key takeaways
3 diagrams · 3 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 49 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Gateway Routing pattern (Azure Architecture Center) (Microsoft)
- Gateway Offloading pattern (Azure Architecture Center) (Microsoft)
- Backends for Frontends pattern (Azure Architecture Center) (Microsoft)
- Throttle requests to your REST APIs for better throughput in API Gateway (Amazon Web Services)
- Amazon API Gateway quotas (Amazon Web Services)
- Gateway API reference (Kubernetes SIG Network) (The Kubernetes Authors)
- RFC 3986: Uniform Resource Identifier (URI): Generic Syntax (IETF)
- HTTP connection manager configuration (Envoy API reference) (Envoy Project)
- Global rate limiting (Envoy documentation) (Envoy Project)
- Rate limit filter configuration (Envoy API reference) (Envoy Project)
- Controlling nginx (nginx)
- OpenID Connect Core 1.0 (section 10.1.1, rotation of asymmetric signing keys) (OpenID Foundation)
- RFC 7662: OAuth 2.0 Token Introspection (IETF)
- Multiple GCP products are experiencing Service issues (incident report) (Google Cloud)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress