Prompt
Design a system where virtual player prices update from a live sports feed and users buy/sell in real time. Thousands of concurrent users; prices must feel live; trades must be correct and fair.
1. Requirements
Functional: ingest live match events → update prices; stream prices to clients; execute buy/sell against a balance; portfolio + leaderboard. Non-functional: low-latency price push (sub-second feel), correctness (no double-spend), fairness (no front-running the feed), high read fan-out.
2. Estimation (shapes the design)
Say 50k concurrent users during a match, each watching ~10 players → a price tick must fan out to many sockets, but the source of truth updates only a few times/sec. Read:write on prices is enormous → compute once, broadcast many.
3. High-level design
- Price engine applies performance jumps on events + small ticks between them, writing the current price to Redis once.
- Fan-out: publish ticks on Redis pub/sub; stateless WebSocket gateways push to clients. N clients ≠ N feed calls, and gateways scale horizontally.
- Trade service validates and settles against Postgres.
4. Deep dive — the two hard parts
Correctness (no double-spend). The balance check must be atomic, not a read-modify-write in app memory. One statement:
UPDATE users SET balance = balance - :cost
WHERE id = :id AND balance >= :cost; -- 0 rows ⇒ insufficient funds, reject
Alternatively optimistic locking (@Version) with retry, or SELECT … FOR UPDATE under high contention.
Fairness (latency arbitrage). The feed lags real life 15–40s, so someone at the venue can front-run it. Mitigations, cheapest first:
- Trade lock: on any event, set
player_trade_lock:{id}in Redis (60s TTL); the trade endpoint rejects locked players — closes the window with one O(1) check. - Delayed execution queue: orders fill 30s later at the price then.
- Sealed batch settlement at a window's median price.
Don't just list fixes — recommend one and justify it: "ship the trade lock first (2 hours, closes the exploit), add anomaly detection, and move to a delayed queue only if stakes rise." Sequencing by cost/impact is the signal.
5. Bottlenecks & scale
- WS gateway memory/conns → shard by player or user; add gateways behind an LB.
- Redis hot key for a star player → replicas / local read cache on gateways.
- Settlement throughput → a queue + idempotent workers (idempotency key per order so retries don't double-execute).
Design drills
Real-time pricing is fan-out + atomicity + fairness. Drill all three.
Whiteboard each one out loud for 5–10 minutes before you reveal what a strong answer covers — the gap between your sketch and the checklist is your study list. Progress is saved on this device.
How does a price get from a match event to thousands of clients in real time?
A player's price must update correctly under many concurrent trades. How do you avoid a lost update?
Some users see the match event before your feed does — latency arbitrage. Defend against it.
Scale the WebSocket gateway to millions of concurrent connections.