fivenines
3/38
Lesson 1.1 99.9%

The system skeleton

🎯 Objectives
  • Name the nine services of the exchange and what each owns.
  • Explain the two traffic planes — request/response and real-time streaming — and why they are separated from day one.
🔗 Connect

From 0.2: exchange core vs. supporting vs. platform. From 0.3: 250 K location writes/s vs. 1 K trip requests/s — a 250:1 ratio. That ratio is why the skeleton has two planes.

We decompose by ownership of state, not by team size or fashion: a service exists where a distinct dataset with distinct consistency needs exists. Nine services result. Each owns its data; no service reads another's tables — at this tier they may still share one physical database cluster, but schemas are private and all cross-service access goes through APIs. That discipline is cheap now and is precisely what makes Parts 2–3 possible without rewrites.

Traffic splits into two planes with wildly different shapes. The request plane (HTTPS, request/response) carries logins, quotes, ride requests, payments — low rate, high value. The streaming plane (WebSocket/gRPC streams) carries driver location pings and live trip updates — 250× the rate, tolerant of a lost message because the next ping arrives in 4 seconds. Mixing them would force the chatty plane's scaling and failure profile onto the valuable one.

flowchart TB
  RA["📱 Rider app"] --> LB["Load balancer + TLS"]
  DA["🚗 Driver app"] --> LB
  LB --> GW["API gateway — authn, rate limits, routing"]
  DA -. "location stream" .-> WS["Realtime gateway — WebSocket"]
  RA -. "live map + trip updates" .-> WS
  subgraph CORE ["Exchange core"]
    LOC["Location service — driver presence, geo index"]
    MATCH["Matching service — dispatch"]
    TRIP["Trip service — lifecycle state machine"]
    PRICE["Pricing service — quotes, surge"]
  end
  subgraph SUPP ["Supporting services"]
    ID["Identity service"]
    PAY["Payments service + ledger"]
    NOTIF["Comms service — push, SMS, chat"]
    RATE["Ratings and history"]
    SUPT["Support and admin"]
  end
  GW --> ID
  GW --> PRICE
  GW --> TRIP
  GW --> MATCH
  GW --> PAY
  GW --> RATE
  GW --> SUPT
  WS --> LOC
  MATCH --> LOC
  MATCH --> TRIP
  PRICE --> LOC
  TRIP --> PAY
  TRIP --> NOTIF
  MATCH --> NOTIF
  subgraph DATA ["State"]
    PG[("PostgreSQL — system of record")]
    RD[("Redis — geo index + cache")]
    Q[["Message queue — async jobs"]]
    S3[("Object store — documents, receipts")]
  end
  CORE --> PG
  SUPP --> PG
  LOC --> RD
  SUPP --> Q
  CORE --> Q
The Part 1 skeleton: two ingress planes, nine services in three groups, four kinds of state. Every later diagram is a zoom into, or a hardening of, this picture.
⚠️ Pitfall

The classic error at this stage is premature microservices: forty services, each 99.9%, chained serially — recall 0.3, you've built a 96% system with extra steps. Nine services with private state and shallow call depth is the disciplined middle.

Next step

See what actually stuck.

Take the practice scenarios now.