Chapter 20: API Gateways & Reverse Proxies
Reverse vs Forward Proxy
Proxy] I1[Internet
Server 1] I2[Internet
Server 2] C1 --> FP C2 --> FP FP --> I1 FP --> I2 end subgraph "Reverse Proxy" CL[Client] RP[Reverse
Proxy] S1[Backend
Server 1] S2[Backend
Server 2] CL --> RP RP --> S1 RP --> S2 end
| Type | Purpose | Who Benefits |
|---|---|---|
| Forward Proxy | Sits in front of clients, forwards requests to internet. Hides client identity, enforces policies, caches content. | Clients (anonymity, filtering) |
| Reverse Proxy | Sits in front of servers, receives client requests. Hides backend topology, load balances, SSL termination. | Servers (protection, scalability) |
API Gateway Pattern
Why Centralize? Cross-cutting concerns (auth, rate limiting, logging) duplicated across services create maintenance burden, inconsistent behavior, and security gaps. Gateway provides single enforcement point.
Gateway] WGW[Web
Gateway] SS1[Auth Service] SS2[Order Service] SS3[Payment Service] M2 --> MGW W2 --> WGW MGW --> SS1 MGW --> SS2 WGW --> SS2 WGW --> SS3 MGW -.optimized for mobile.-> MGW WGW -.optimized for web.-> WGW end
BFF Pattern: Separate gateway per client type (mobile, web, IoT). Each gateway aggregates, transforms data optimally for its client. Mobile gateway returns compact JSON, handles high latency. Web gateway returns richer data.
Core Functions
| Function | Purpose | Example |
|---|---|---|
| Routing | Forward requests to appropriate backend based on path, headers, or query params | /api/v2/users/* → UserServiceV2 |
| Rate Limiting | Throttle requests per client to prevent abuse, ensure fair resource allocation | 1000 req/min per API key |
| Authentication | Validate credentials (JWT, API keys) before forwarding to services | Verify JWT signature, check expiry |
| Authorization | Enforce access control policies based on user roles, scopes | Require admin scope for DELETE |
| Transformation | Modify request/response (protocol conversion, data aggregation, field mapping) | SOAP → REST, combine 3 service calls |
| Caching | Store frequent responses to reduce backend load, improve latency | Cache GET /products for 60s |
| Circuit Breaking | Fail fast when backend unhealthy, prevent cascading failures | Open circuit after 5 consecutive 500s |
| SSL Termination | Decrypt HTTPS at gateway, forward HTTP internally (reduces backend CPU) | TLS 1.3 termination |
| Observability | Centralized logging, metrics, tracing for all API traffic | Log all 4xx/5xx with trace IDs |
Rate Limiting
Token Bucket Algorithm
Capacity: 100
Current: 47] R[Refill Rate
10 tokens/sec] REQ[Request
needs 1 token] R -.add tokens.-> B REQ -.take token.-> B end subgraph "States" A[Token Available
✓ Allow Request] D[Bucket Empty
✗ Deny Request] end
| Algorithm | How It Works | Pros/Cons |
|---|---|---|
| Token Bucket | Bucket holds tokens, refills at fixed rate. Request consumes token. Allows bursts up to capacity. | + Burst traffic OK - Slightly complex |
| Leaky Bucket | Requests enter queue, processed at fixed rate. Excess requests overflow (rejected). | + Smooth output - No bursts |
| Fixed Window | Count requests per time window (e.g., per minute). Reset counter at window boundary. | + Simple - Burst at edges (2x limit) |
| Sliding Window | Weighted count from current + previous window. Smooth rate enforcement. | + No edge bursts - More computation |
Distributed Rate Limiting
- Redis Centralized: All gateways check Redis for token count. Atomic INCR/DECR ensures consistency. Single point of truth but adds latency.
- Local + Sync: Each gateway maintains local counter, periodically syncs with Redis. Lower latency, eventual consistency, potential overage.
- Sticky Sessions: Route same API key to same gateway instance. No coordination needed but uneven load distribution.
Auth Offloading
JWT Validation at Gateway: Gateway verifies JWT signature, expiry, issuer. Extracts claims (user ID, roles), forwards as headers to backend. Backend trusts gateway, skips re-validation.
Authorization: Bearer JWT G->>G: Verify JWT signature
Check expiry, issuer alt Invalid JWT G->>C: 401 Unauthorized else Valid JWT G->>G: Extract claims
(user_id, roles) G->>B: GET /orders
X-User-Id: 12345
X-Roles: admin,user B->>B: Trust gateway,
skip auth check B->>G: 200 OK + orders G->>C: 200 OK + orders end
API Key Management
- Storage: Store hashed keys in database/cache (Redis). Never log plaintext keys.
- Rotation: Support dual keys during rotation window. Deprecate old key after grace period.
- Scopes: Attach permissions to keys (read-only, admin). Gateway enforces before routing.
Routing Strategies
| Strategy | Rule | Use Case |
|---|---|---|
| Path-Based | /api/users/* → UserService/api/orders/* → OrderService |
Microservices routing |
| Header-Based | X-API-Version: 2 → V2 backendX-Client-Type: mobile → Mobile BFF |
Versioning, client-specific |
| Query Param | ?version=beta → Beta cluster |
A/B testing, opt-in features |
| Canary (Weighted) | 95% → Stable, 5% → Canary | Gradual rollout, risk mitigation |
| Geographic | EU traffic → EU region, US → US | Latency, data residency |
| Content-Based | Parse request body, route by field (e.g., country) |
Complex routing logic |
Canary Deployments
Route small percentage of traffic to new version. Monitor error rates, latency. Gradually increase percentage or rollback if issues detected.
# Kong canary config
services:
- name: order-service-v1
weight: 95
- name: order-service-v2
weight: 5
Implementations
| Product | Type | Strengths | Weaknesses | Best For |
|---|---|---|---|---|
| Kong | Open-source, Nginx-based | Plugin ecosystem, high perf, K8s native, declarative config | Complex setup, paid enterprise features | Microservices, hybrid cloud |
| AWS API Gateway | Managed service | Zero ops, auto-scale, native AWS integration (Lambda, Cognito) | Vendor lock-in, cold start latency, cost at scale | Serverless, AWS-heavy orgs |
| Apigee | Enterprise platform | Advanced analytics, monetization, developer portal, SLA mgmt | Expensive, overkill for small teams | API products, external partners |
| Envoy | L7 proxy (CNCF) | Service mesh ready, observability, gRPC/HTTP2, dynamic config | Low-level, steep learning curve | Service mesh (Istio), modern protocols |
| Nginx | Reverse proxy | Battle-tested, high perf, simple config, wide adoption | Limited API features, manual scaling | Load balancing, SSL termination |
| Traefik | Cloud-native proxy | Auto-discovery (K8s, Docker), easy config, Let's Encrypt integration | Less mature, fewer enterprise features | Dynamic environments, small teams |
| Azure API Mgmt | Managed service | Hybrid/multi-cloud, dev portal, Azure integration | Azure-centric, cost | Enterprise Azure shops |
| Tyk | Open-source | GraphQL support, multi-tenant, dashboard | Smaller community | GraphQL APIs, multi-tenant SaaS |
Gateway vs Service Mesh
Clients] GW[API
Gateway] S1[Service A] S2[Service B] EXT --> GW GW --> S1 GW --> S2 end subgraph "East-West (Service Mesh)" SA[Service A
+ Sidecar] SB[Service B
+ Sidecar] SC[Service C
+ Sidecar] SA <--> SB SB <--> SC SA <--> SC end
| Aspect | API Gateway | Service Mesh |
|---|---|---|
| Traffic Direction | North-South (external → internal) | East-West (service → service) |
| Primary Role | Entry point, auth, rate limiting, routing to services | Service-to-service communication, mTLS, retries, observability |
| Deployment | Centralized (1-N instances at edge) | Decentralized (sidecar per service) |
| Focus | API management, external clients, aggregation | Reliability, security, observability between services |
| Examples | Kong, AWS API Gateway, Apigee | Istio, Linkerd, Consul Connect |
Use Both: Gateway handles external traffic (auth, rate limit). Service mesh handles internal traffic (mTLS, retries, circuit breaking). Gateway can integrate with mesh for unified observability.
API Versioning
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL Path | /api/v1/users/api/v2/users |
Explicit, cache-friendly, easy routing | URL proliferation, not RESTful purist |
| Header | Accept: application/vnd.api+json; version=2X-API-Version: 2 |
Clean URLs, RESTful, flexible | Hidden, harder to test (curl), caching complex |
| Query Param | /api/users?version=2 |
Simple, backward compatible (default version) | Pollutes query space, easy to omit |
Best Practice: URL path versioning most common. Reserve major versions for breaking changes. Use feature flags for minor changes within version.
Edge Security
Web Application Firewall (WAF)
- SQL Injection: Detect patterns like
' OR 1=1--in query params, body - XSS: Block
<script>tags, JavaScript event handlers in input - Path Traversal: Reject
../../../etc/passwdattempts - OWASP Top 10: Rulesets for common web vulnerabilities
DDoS Protection
- Rate Limiting: Aggressive limits for unauthenticated traffic
- SYN Flood: SYN cookies, connection limits at L4
- HTTP Flood: Challenge-response (CAPTCHA), JS challenge
- CDN: Distribute load globally (Cloudflare, Akamai absorb volumetric attacks)
Bot Detection
- Behavioral Analysis: Mouse movement, keystroke timing
- Fingerprinting: Browser, device, IP reputation
- CAPTCHA: reCAPTCHA v3 (invisible score-based)
- Rate Anomalies: Flag IPs making 1000s of requests
Mutual TLS (mTLS)
- Client Certificates: Gateway validates client cert before allowing connection
- Zero Trust: Every service authenticates, not just perimeter
- Service Mesh: Automatic mTLS between services (Istio, Linkerd)
IP Whitelisting/Blacklisting
- Whitelist: Only allow traffic from trusted IPs (partner APIs, internal tools)
- Blacklist: Block known malicious IPs (threat intel feeds)
- Geo-Blocking: Block entire countries if not serving those regions
Summary
| Concept | Key Takeaway |
|---|---|
| Reverse Proxy | Sits in front of servers, hides backend topology |
| API Gateway | Centralized cross-cutting concerns: auth, rate limiting, routing |
| BFF | Backend for Frontend: separate gateway per client type |
| Rate Limiting | Token bucket allows bursts, sliding window prevents edge bursts |
| Auth Offloading | Validate once at gateway, forward claims to backend |
| Gateway vs Mesh | North-South (external) vs East-West (internal) |
| Versioning | URL path (/v1, /v2) most practical |
| Edge Security | WAF, DDoS, bot detection, mTLS at gateway |
Rate Limiting Implementation
Sliding Window Counter Algorithm
FUNCTION is_allowed(user_id, limit, window_sec):
current_window = FLOOR(current_time / window_sec)
key = "rate:" + user_id + ":" + current_window
// Increment counter for current window
count = REDIS.INCR(key)
IF count == 1:
REDIS.EXPIRE(key, window_sec * 2) // Keep for 2 windows
// Sliding window: weighted average of previous + current
previous_window = current_window - 1
prev_key = "rate:" + user_id + ":" + previous_window
prev_count = REDIS.GET(prev_key) OR 0
// Calculate elapsed time in current window
elapsed = current_time MOD window_sec
weight = (window_sec - elapsed) / window_sec
// Effective count = weighted previous + current
effective_count = (prev_count * weight) + count
RETURN effective_count <= limit
Example Walkthrough
| Time | Current Window | Previous Window | Weight | Effective Count | Result (limit=100) |
|---|---|---|---|---|---|
| 00:00:30 | 50 requests | 80 requests | 0.5 (30s into 60s window) | 80 * 0.5 + 50 = 90 | Allow (90 <= 100) |
| 00:00:45 | 70 requests | 80 requests | 0.25 (45s into 60s window) | 80 * 0.25 + 70 = 90 | Allow (90 <= 100) |
| 00:00:50 | 85 requests | 80 requests | 0.17 (50s into 60s window) | 80 * 0.17 + 85 = 98.6 | Allow (98.6 <= 100) |
| 00:00:55 | 95 requests | 80 requests | 0.08 (55s into 60s window) | 80 * 0.08 + 95 = 101.4 | Deny (101.4 > 100) |
Redis Implementation
# Atomic sliding window check in Redis (Lua script)
local current_key = KEYS[1]
local previous_key = KEYS[2]
local limit = tonumber(ARGV[1])
local window_sec = tonumber(ARGV[2])
local weight = tonumber(ARGV[3])
local current = redis.call('INCR', current_key)
if current == 1 then
redis.call('EXPIRE', current_key, window_sec * 2)
end
local previous = tonumber(redis.call('GET', previous_key) or 0)
local effective = (previous * weight) + current
if effective <= limit then
return {1, effective} -- allowed
else
redis.call('DECR', current_key) -- rollback increment
return {0, effective} -- denied
end
Performance: Redis INCR is O(1), entire check completes in 1-2ms at P99. For 10K requests/sec, needs ~20MB memory per million users (2 keys * 8 bytes * 1M users).
Circuit Breaker at Gateway
Purpose: Prevent cascading failures when backend service degrades. Gateway fails fast instead of waiting for timeouts, reducing resource exhaustion.
(e.g., 50% of last 10 requests failed) OPEN --> HALF_OPEN : wait_duration elapsed
(default 30s) HALF_OPEN --> CLOSED : probe requests succeed
(e.g., 3/3 successful) HALF_OPEN --> OPEN : probe request fails CLOSED --> CLOSED : request succeeds OPEN --> OPEN : request blocked (immediate 503) note right of CLOSED: Normal operation, Track failures in sliding window note right of OPEN: All requests rejected immediately, No backend calls, Timer runs for wait_duration note right of HALF_OPEN: Limited probe requests allowed, Test if backend recovered
State Machine Logic
CLASS CircuitBreaker:
state = CLOSED
failure_count = 0
success_count = 0
last_failure_time = null
request_log = [] // Last N requests
FUNCTION call(request):
IF state == OPEN:
IF current_time - last_failure_time > wait_duration:
state = HALF_OPEN
success_count = 0
ELSE:
RETURN error("503 Service Unavailable - Circuit Open")
IF state == HALF_OPEN:
IF success_count >= permitted_calls_in_half_open:
state = CLOSED
failure_count = 0
request_log = []
TRY:
response = backend.call(request, timeout=slow_call_threshold)
record_success()
RETURN response
CATCH TimeoutException, ServerError:
record_failure()
IF should_open_circuit():
state = OPEN
last_failure_time = current_time
THROW error
FUNCTION record_failure():
request_log.append({success: false, time: current_time})
request_log = request_log[-10:] // Keep last 10
FUNCTION should_open_circuit():
IF request_log.length < 10: RETURN false
failures = COUNT(request_log WHERE success == false)
RETURN failures / request_log.length >= failure_threshold
Configuration Parameters
| Parameter | Default | Purpose | Tuning Guidance |
|---|---|---|---|
| failure_threshold | 50% | Percentage of requests that must fail to open circuit | Lower (30%) for critical services, higher (70%) for flaky services |
| slow_call_threshold | 5 seconds | Request duration considered a "slow call" failure | Set to 2x typical P99 latency |
| wait_duration | 30 seconds | How long circuit stays OPEN before trying HALF_OPEN | Longer (60s) for databases, shorter (10s) for stateless services |
| permitted_calls_in_half_open | 3 | Number of probe requests allowed in HALF_OPEN state | Higher (10) for high-traffic endpoints |
| sliding_window_size | 10 requests | Number of recent requests to track for failure rate | Larger (100) for high-volume, smaller (5) for low-traffic |
Real-World Example
Scenario: Payment service starts timing out (database overload). Gateway circuit breaker opens after 5/10 requests timeout. For next 30 seconds, gateway returns 503 immediately without calling payment service. After 30s, allows 3 probe requests. If successful, closes circuit and resumes normal traffic.
Impact: Without circuit breaker, all gateway threads block on payment timeouts (5s each), exhausting thread pool. With circuit breaker, gateway remains responsive, other services unaffected.
Request/Response Transformation
Header Manipulation
Request Headers (Gateway → Backend)
// Strip internal/sensitive headers before forwarding
REMOVE: X-Forwarded-Proto, X-Real-IP, Authorization
ADD: X-Request-ID: generate_uuid()
X-User-Id: extract_from_jwt(Authorization)
X-Client-IP: client.ip_address
X-Gateway-Time: current_timestamp()
// Example transformation
BEFORE (client → gateway):
GET /api/orders
Authorization: Bearer eyJhbGc...
User-Agent: Mozilla/5.0
X-Custom-Header: untrusted_value
AFTER (gateway → backend):
GET /api/orders
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
X-User-Id: 12345
X-Roles: admin,user
X-Client-IP: 203.0.113.42
User-Agent: Mozilla/5.0
// Authorization removed, X-Custom-Header removed
Response Headers (Backend → Gateway → Client)
// Remove internal details
REMOVE: X-Backend-Server, X-Database-Query-Time, X-Internal-Cache-Hit
// Add security headers
ADD: X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
// Add CORS headers (if configured)
ADD: Access-Control-Allow-Origin: https://trusted-domain.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Response Aggregation (BFF Pattern)
Problem: Mobile app needs user profile, order history, and recommendations. Without gateway, makes 3 sequential API calls (high latency on cellular).
// Gateway aggregation endpoint
GET /api/mobile/dashboard
GATEWAY LOGIC:
// Parallel backend calls
PARALLEL:
user = GET https://user-service/api/users/{user_id}
orders = GET https://order-service/api/orders?user_id={user_id}&limit=5
recommendations = GET https://recommendation-service/api/recommendations/{user_id}
// Combine responses
RETURN {
"user": {
"name": user.name,
"avatar": user.avatar_url,
"tier": user.membership_tier
},
"recent_orders": orders.items.map(o => {
"id": o.id,
"status": o.status,
"total": o.total_amount
}),
"recommended_products": recommendations.slice(0, 3)
}
// Result: 3 backend calls in parallel (200ms), 1 client round-trip
// vs 3 sequential client calls (600ms + mobile latency)
Protocol Conversion
| Client Protocol | Backend Protocol | Use Case |
|---|---|---|
| REST (HTTP/JSON) | gRPC (HTTP/2 + Protobuf) | External REST API backed by internal gRPC microservices |
| REST | SOAP (XML) | Modern web frontend calling legacy SOAP services |
| GraphQL | Multiple REST APIs | Unified GraphQL schema over disparate REST backends |
| WebSocket | HTTP polling | Real-time client notifications from polling-based backend |
Field Mapping & Data Enrichment
// Backend returns internal field names
BACKEND RESPONSE:
{
"usr_id": 12345,
"crt_dt": "2026-05-12T10:30:00Z",
"ord_amt": 5999,
"curr_cd": "USD"
}
// Gateway transforms to client-friendly names + enrichment
GATEWAY RESPONSE:
{
"user_id": 12345,
"created_date": "2026-05-12T10:30:00Z",
"order_amount": 59.99, // cents to dollars
"currency": "USD",
"formatted_amount": "$59.99", // added by gateway
"created_relative": "2 hours ago" // computed
}
Production Challenges & Solutions
| Challenge | Impact | Solution | Implementation Details |
|---|---|---|---|
| Single Point of Failure | Gateway down = entire API unavailable, regardless of backend health | Active-active gateway cluster with health checks | Deploy 3+ instances behind L4 load balancer. Health check endpoint /health every 5s. Remove unhealthy instances from pool. Use anycast IP for global failover. |
| Latency Overhead | Every request adds gateway processing time (auth, logging, routing). P99 latency increases by 10-50ms. | Optimize hot path, measure at P99, connection pooling | Keep-alive connections to backends (reuse TCP). Cache JWT validation results (5-10s). Async logging (don't block on log writes). Profile regularly - aim for <5ms P99 overhead. |
| Config Propagation Delay | New route rules take 30-60s to reach all gateway instances. Inconsistent behavior during rollout. | Eventual consistency OK for routes, immediate for security | Route changes: poll config every 30s (acceptable). Security rules (IP blocks, revoked keys): push via Redis pub/sub (immediate). Use versioned configs to avoid partial updates. |
| Memory Pressure from Rate Limit Counters | 1M active users × 2 rate limit keys × 1KB = 2GB memory. Keys never expire if user inactive. | LRU eviction, approximate counters (HyperLogLog) | Redis LRU eviction policy (volatile-lru). Use 8-byte counters, not hash structures. Probabilistic counters (HyperLogLog) for large cardinality (1% error, 10x memory savings). TTL on keys (2x window duration). |
| SSL Termination CPU Cost | TLS handshake is CPU-intensive (RSA 2048-bit ~1ms/handshake). 10K handshakes/sec = 10 cores. | Hardware offload, session tickets, connection reuse | Use ECDSA certs (faster than RSA). Enable TLS session tickets (skip full handshake on reconnect). HTTP/2 connection reuse. Hardware accelerators (AWS Nitro, Intel QAT) offload crypto. Keep-alive for 60s+. |
| Thundering Herd on Cache Miss | Cache expires, 1000 concurrent requests hit backend simultaneously. Backend overloads. | Request coalescing (deduplication), probabilistic early expiration | Coalesce identical in-flight requests (only 1 backend call for cache miss). Probabilistic early refresh: 5% of requests refresh cache before expiry. Jitter on TTL (±10%) to spread expirations. |
| Backend Service Discovery | Hardcoded backend IPs become stale when services scale or fail. Manual updates error-prone. | Dynamic service discovery (Consul, K8s service mesh) | Integrate with Consul DNS, K8s services, or Envoy xDS. Gateway polls service registry every 10s. Graceful removal: drain connections over 30s before deregistering. DNS TTL = 10s max. |
| Partial Backend Failures | In response aggregation, if 1 of 3 backends fails, entire request fails. Poor user experience. | Graceful degradation, return partial data with status | Return HTTP 200 with partial data + metadata: {"user": {...}, "orders": null, "recommendations": {...}, "errors": {"orders": "timeout"}}. Client decides if usable. Set per-service timeout (fast services 100ms, slow 500ms). |
| Config Drift Across Environments | Dev/staging/prod gateways have inconsistent configs. "Works in staging" bugs. | Infrastructure as code, automated config validation | Store configs in Git (Kong declarative config, Terraform). CI/CD validates syntax, runs integration tests. Promote configs dev → staging → prod. Dry-run mode in prod before applying. |
Capacity Planning
| Metric | Guideline | Example Calculation |
|---|---|---|
| Throughput | 1 gateway instance handles 5-10K req/sec (simple routing) | 50K peak req/sec → 5-10 instances (N+2 redundancy) |
| Connection Pool | Pool size = peak concurrent requests / num instances | 1000 concurrent × 10 backends / 5 instances = 2000 connections/instance |
| Memory | Base 512MB + (rate limit keys × 8 bytes) + (connection overhead 1KB each) | 512MB + (100K users × 16B) + (2000 conn × 1KB) = 512MB + 1.6MB + 2MB = 516MB |
| CPU | 2-4 cores per instance (1 core = 2K req/sec) | 10K req/sec → 4-5 cores per instance |
Gateway Observability
Why Critical: Gateway sees ALL traffic, making it ideal aggregation point for metrics. Centralized observability eliminates per-service instrumentation duplication.
Key Metrics to Monitor
| Metric | Purpose | Threshold/Alert | Diagnostic Value |
|---|---|---|---|
| Latency Histogram (P50/P95/P99) per Route | Identify slow endpoints, detect degradation | Alert if P99 > 2x baseline for 5 minutes | P99 spike but P50 stable = outliers (DB query timeout?). All percentiles spike = systemic issue. |
| Error Rate by Upstream Service | Detect failing backends, isolate problem service | Alert if 5xx rate > 1% for any service | Sudden spike = deployment issue. Gradual increase = resource exhaustion. 503 from gateway = circuit open. |
| Rate Limit Hit Rate | Validate limits are appropriate, detect abuse | Review if hit rate > 20% (limits too tight) or <0.1% (limits ineffective) | Single user/IP hitting limits = bot or bug. Widespread hits = limits misconfigured. |
| Connection Pool Utilization | Ensure adequate capacity, prevent pool exhaustion | Alert if utilization > 80% for 2 minutes | High utilization + slow backend = increase pool size. High utilization + fast backend = need more gateway instances. |
| Active Requests (In-Flight) | Detect stuck connections, load spikes | Alert if active requests > 2x baseline | Steady high count = backend slow/hanging. Rapid increase = traffic spike or circuit breaker needed. |
| Circuit Breaker State Changes | Track backend health, alert on failures | Alert immediately on OPEN state | Frequent OPEN → CLOSED → OPEN = flapping (adjust thresholds). Stuck OPEN = backend down. |
| Request Size Distribution | Detect large payloads, potential DoS | Alert if P99 request size > 10MB | Sudden large payloads = upload attack or misconfigured client. Set max body size limits (e.g., 5MB). |
| Upstream Response Time by Service | Isolate which backend is slow | Dashboard visualization, no alert (latency histogram covers this) | Gateway adds 5ms, ServiceA adds 200ms, ServiceB adds 50ms → optimize ServiceA. |
| Auth Failure Rate | Detect credential stuffing, token expiry issues | Alert if 401 rate > 5% of total requests | Sudden spike from single IP = brute force attack (block IP). Widespread 401s = token service outage. |
| Cache Hit Rate | Validate caching effectiveness | Review if hit rate < 60% (cache misses too frequent) | Low hit rate = short TTL or high data variability. High hit rate but slow = cache backend overloaded (use local cache). |
Distributed Tracing
// Gateway adds trace context to all backend requests
REQUEST from client:
GET /api/orders
GATEWAY forwards with trace headers:
GET /api/orders
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
X-B3-TraceId: 80f198ee56343ba864fe8b2a57d3eff7
X-B3-SpanId: e457b5a2e4d86bd1
X-B3-Sampled: 1
// Trace spans:
1. Gateway receives request: 0ms
2. Gateway auth check: 0-3ms
3. Gateway → Order Service: 3-150ms
- Order Service DB query: 10-140ms
4. Gateway → Inventory Service: 3-80ms (parallel)
5. Gateway response aggregation: 150-155ms
6. Gateway sends response: 155ms
// Trace shows: Total 155ms, Order Service DB query is bottleneck (130ms)
Logging Best Practices
| Log What | Log Level | Sample Rate | Example |
|---|---|---|---|
| All 4xx/5xx responses | WARN / ERROR | 100% | {"status": 503, "route": "/api/orders", "upstream": "order-service", "error": "circuit open"} |
| Successful requests | INFO | 1-10% (sampling) | {"status": 200, "route": "/api/users", "latency_ms": 45, "user_id": 12345} |
| Rate limit violations | WARN | 100% | {"status": 429, "user_id": 12345, "limit": 1000, "window": "1m", "ip": "203.0.113.42"} |
| Auth failures | WARN | 100% | {"status": 401, "reason": "jwt_expired", "ip": "203.0.113.42", "user_agent": "..."} |
| Circuit state changes | ERROR | 100% | {"event": "circuit_opened", "service": "payment-service", "failure_rate": 0.65} |
| Config reloads | INFO | 100% | {"event": "config_reloaded", "version": "v123", "routes_added": 2, "routes_removed": 0} |
Dashboard Example Panels
- Traffic Overview: Total req/sec, 2xx/4xx/5xx breakdown (stacked area chart)
- Latency Heatmap: Histogram over time, color-coded by percentile
- Top Slow Routes: Table sorted by P99 latency, link to trace examples
- Error Rate by Service: Line chart per backend service, alert threshold line
- Circuit Breaker Status: Current state per service (CLOSED/OPEN/HALF_OPEN), time in state
- Rate Limit Hit Rate: Percentage of requests rate-limited, by user tier (free vs paid)
- Active Connections: Gauge showing current vs max pool size, per backend
Alerting Strategy
| Severity | Trigger | Response Time | Example |
|---|---|---|---|
| P0 (Critical) | Gateway down, all traffic failing | Immediate page | Health check fails on all instances, 100% 503 rate |
| P1 (High) | Circuit breaker opens, major service degraded | Page within 5 min | Payment service circuit OPEN, 50% of checkout requests fail |
| P2 (Medium) | Elevated error rate, high latency | Slack notification | P99 latency > 2s for 5 minutes, 5xx rate 3% |
| P3 (Low) | Anomalies, potential issues | Dashboard review | Rate limit hit rate increased to 25%, cache hit rate dropped to 50% |