Chapter 20: API Gateways & Reverse Proxies

Reverse vs Forward Proxy

graph LR subgraph "Forward Proxy" C1[Client 1] C2[Client 2] FP[Forward
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)
Interview Tip: Forward proxy protects clients, reverse proxy protects servers. API gateways are specialized reverse proxies.

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.

graph TB subgraph "Without Gateway" M1[Mobile App] W1[Web App] S1[Auth Service] S2[Order Service] S3[Payment Service] M1 -.auth, rate limit, log.-> S1 M1 -.auth, rate limit, log.-> S2 W1 -.auth, rate limit, log.-> S2 W1 -.auth, rate limit, log.-> S3 end subgraph "Backend for Frontend (BFF) Pattern" M2[Mobile App] W2[Web App] MGW[Mobile
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.

Interview Tip: BFF prevents mobile clients from making 10+ API calls by aggregating at gateway layer. Reduces latency, battery drain.

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

graph LR subgraph "Token Bucket" B[Bucket
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

Interview Tip: Fixed window has "burst at boundary" problem: client can send 1000 req at 00:59 and 1000 at 01:00 (2000 in 2 seconds). Sliding window prevents this.

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.

sequenceDiagram participant C as Client participant G as Gateway participant A as Auth Service participant B as Backend Service C->>G: GET /orders
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

Interview Tip: Auth offloading reduces latency (no repeated auth service calls) but creates trust boundary. Backend must trust gateway headers aren't spoofed. Use internal network or mTLS.

Routing Strategies

Strategy Rule Use Case
Path-Based /api/users/* → UserService
/api/orders/* → OrderService
Microservices routing
Header-Based X-API-Version: 2 → V2 backend
X-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
Interview Tip: Nginx is best for simple reverse proxy + load balancing. Kong/Envoy for advanced routing, auth, observability. AWS API Gateway for serverless with zero ops.

Gateway vs Service Mesh

graph TB subgraph "North-South (Gateway)" EXT[External
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.

Interview Tip: Gateway is perimeter defense (firewall at border). Service mesh is zero-trust internal security (every service authenticates). Often used together.

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=2
X-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.

Interview Tip: URL versioning (/v1, /v2) wins for simplicity and visibility. Header versioning preferred by REST purists but harder for clients to discover and debug.

Edge Security

Web Application Firewall (WAF)

DDoS Protection

Bot Detection

Mutual TLS (mTLS)

IP Whitelisting/Blacklisting

Interview Tip: Gateway is ideal place for WAF, DDoS protection. Single enforcement point, no code changes to services. Offload security to specialized layer.

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)
Interview Tip: Fixed window has "boundary burst" problem. At 00:59:50, client sends 100 requests (allowed). At 01:00:00, window resets, client sends another 100 (allowed). Result: 200 requests in 10 seconds, double the intended limit. Sliding window prevents this by weighting the previous window, ensuring smooth rate enforcement across boundaries.

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.

stateDiagram-v2 [*] --> CLOSED CLOSED --> OPEN : failure_threshold exceeded
(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.

Interview Tip: Circuit breaker is NOT retry logic. Retry amplifies load on failing service (worse!). Circuit breaker STOPS sending requests, giving backend time to recover. Use retry for transient errors (network blip), circuit breaker for sustained failures (service degraded).

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
}
Interview Tip: Response aggregation at gateway reduces client complexity and mobile battery drain (fewer radio activations). But adds latency if done serially - always parallelize backend calls with timeout handling. If one service is slow, consider returning partial response or cached data.

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
Interview Tip: Gateway failures are HIGH IMPACT (blast radius = entire API). Always run N+2 instances (handle 2 simultaneous failures). Monitor failover time - should be <5 seconds. Practice chaos engineering: kill random gateway instance, verify traffic shifts seamlessly.

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

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%
Interview Tip: Gateway observability is "single pane of glass" for entire API. One dashboard shows health of all services, no need to check N service dashboards. But gateway metrics only show symptoms (high latency), not root cause (DB lock contention) - still need backend service observability for deep debugging. Use trace IDs to correlate gateway → backend logs.