Skip to content
Episode 36 · Kafka for 750 reviews a day

The system design method

Tuesday, 10:05: Maya wants guest reviews on every hotel page in four weeks, and within ten minutes the whiteboard shows Kafka, Cassandra and five new services. StayHub gets about 750 reviews a day.

A simple, repeatable recipe for designing any system, so you build what's needed: not too much, not too little.

30 min intermediateThe system design methodFunctional vs non-functional requirementsBack-of-the-envelope capacity estimationAPI as a contractFinding the bottleneckTrade-off thinking
  1. Problem

    You're asked to 'design a reviews system' for a hotel platform, in an interview or a real kickoff, and the problem is huge and vague.

  2. Why the simple system fails

    Jumping straight to boxes (Kafka! Cassandra! microservices!) produces a design that solves imagined problems, misses real requirements and can't be defended.

  3. Engineering concept

    Work in a fixed order: requirements → functional/non-functional → estimates → APIs → data model → high-level design → deep dives → trade-offs. Each step constrains the next.

  4. Technology

    Requirement lists, back-of-the-envelope numbers, API sketches (REST), schema sketches (SQL), component diagrams (load balancer, app servers, cache, DB, queue, CDN).

  5. Trade-offs

    Time spent clarifying versus time drawing; a simple design that meets the numbers versus an impressive one that doesn't. Every component must be justified by a requirement.

01

The problem

2 min
Tuesday, 10:05. The reviews kickoff.
  1. Maya · Product manager
    Guests ask: is this hotel actually good? I want reviews on every hotel page in four weeks.
  2. You · Engineer at StayHub
    Theo already drew it: Kafka, Cassandra, five services. It looks serious.
  3. Lina · Senior engineer, your mentor
    It does. How many reviews a day do you think we'll get?
02

The big idea

4 min
Boxes before questions
The big idea

Questions → numbers → boxes. Every component in a design must point back to a requirement or a number that demands it.

An architect asks how many people live here and how cold the winters get before drawing a floor plan. A family of five isn't a studio flat. Debating Kafka before the numbers is debating an answer before the question.

03

How it works

5 min

Eight steps, each feeding the next: clarify scope; split requirements (what it does) from ones (how well: speed, uptime, never losing data, cost); estimate users → → storage, average and peak; sketch the API; the data model; the high-level design; deep-dive the one to three hardest parts; name the trade-offs and what changes at 10×.

Lina runs it on reviews, fast
  1. 1
    Ask

    Only guests with a finished stay. Text + up to 5 photos. Hotels can reply. Translation is out of scope.

  2. 2
    How well

    Reviews block under 200 ms at p99. A submitted review is never lost. Visible within minutes is fine.

  3. 3
    Estimate

    ~750 reviews/day ≈ 0.01 writes/s. ~15 page reads/s at peak. ~270 MB of text a year.

  4. 4
    Data

    A reviews table in our PostgreSQL, one review per booking, indexed for "hotel X, newest first".

  5. 5
    Boxes

    A reviews module in the existing app, Redis for summaries, photos on the CDN. Nothing new to run.

  6. 6
    Trade-offs

    Summaries lag by minutes. Revisit if reviews pass ~50 writes/s.

0.01 writes a second rules out Kafka and Cassandra on its own. The non-functional target, not the features, pointed to the one thing worth care: the hotel page must stay fast.

04

Watch a decision being made

4 min
Worked example

Lina designs StayHub reviews

Reviews (rating, text, photos, hotel replies) on every hotel page within four weeks, on our modular monolith and PostgreSQL, with a plan for 10× growth.

  1. 1Estimate, and say what each number changes

    Reads ~15/s at peak (150 at 10×), writes 0.01/s, text ~270 MB/year, photos ~135 GB/year.

    text
    reads:  120,000 views/day ÷ 86,400 s ≈ 1.4/s → peak ×10 ≈ 15/s
    writes: 2,500 bookings × 30% = 750/day ≈ 0.01/s
    photos: 750 × 500 KB × 365 ≈ 135 GB/year → object storage + CDN

    Why: An estimate that doesn't change the design is decoration. These rule out new databases and send photo bytes away from the app servers.

  2. 2Write the API and the table

    GET /hotels/{id}/reviews?cursor=, POST /reviews with an idempotency key. reviews(…, booking_id UNIQUE, rating CHECK 1–10, …), index on (hotel_id, status, created_at DESC).

    Why: booking_id UNIQUE makes "one review per stay" a rule the database enforces; the index matches the hottest query.

  3. 3Deep-dive the hot read path

    AVG(rating) over 3,000 reviews per page view costs ~90 ms. Instead, the moderation job updates a rating_summary row on approval and clears its Redis key: reads take ~2–6 ms.

    Why: This path runs on every hotel page and has a target. Submission runs every two minutes and needs no cleverness.

  4. 4Name the trade-off and the trigger

    Summaries can lag a few minutes. Revisit if writes pass ~50/s, or reviews need their own deploys.

    Why: Every design gives something up. A written trigger means nobody guesses later when it stopped fitting.

The whole thing in one line: Numbers (0.01 writes/s, 15 reads/s at peak) ruled out Kafka and Cassandra; the effort went to the one hot path, a cached summary that keeps hotel pages under 200 ms.
05

In real life

3 min
Same method, very different answers

Stack Overflow: a few big servers

  • Has publicly described serving a huge site from a small number of servers
  • Their numbers said a few machines and heavy caching were enough

Fits load that fits a few machines and a team wanting less to run.

Netflix: many services in the cloud

  • Has publicly described splitting into many small cloud services
  • Hundreds of teams and strict availability needs; lots of tooling

Fits when many teams and strict uptime justify the complexity.

06

What goes wrong

3 min
Drawing boxes in the first minute

You design for assumptions nobody stated.

Instead

First minutes on users, scope and non-functional targets.

Estimating without using the numbers

0.01 writes/s followed by Cassandra shows the estimate was decoration.

Instead

State each number's consequence: "so one table".

Deep-diving the easy part

Ten minutes on submit while the hot read goes unexamined.

Instead

Go deep first where it's hardest and riskiest.

07

Trade-offs

3 min
ApproachFits whenYou pay
Start simple, evolveEstimates fit one database; requirements uncertainRedesign work later if growth is wild
Build for projected scale nowScale is near-certain; a late migration would be very costlyCost and complexity today, slower delivery
Tuesday, 15:00. Karim reads the design doc.
  1. Karim · CTO
    Investors will ask about 10×. Build reviews as its own service now, so we never migrate.
  2. Lina · Senior engineer, your mentor
    At 10× it's 0.1 writes a second and 150 reads. Still one table. A service is another pipeline and on-call rota for 14 people.
  3. Karim · CTO
    And when we outgrow it, the migration lands on the worst possible week.
08

Your job as tech lead

4 min

Claude Code can write the code for this. You still own the decisions, and you have to know what good looks like to accept or reject its work.

Decisions you own
  • The non-functional requirements (under 200 ms p99, never lose a review, visible within minutes) and the input numbers. Never let Claude invent traffic.
  • Which boxes the design may add: a module in the existing app by default; a new database or queue needs a number that demands it.
  • Where the deep dive goes (the hot read path), the trade-off you accept, and the revisit trigger (~50 writes/s).
Tell Claude Code
Maya wants guest reviews on hotel pages: rating, text, photos, replies, moderation, one per booking. No code yet. Write docs/design/reviews.md: requirements (reviews block under 200 ms p99, never lose a review, visible within minutes), estimates from our numbers only (~120,000 page views/day, ~750 reviews/day, peak 10× average), API, table, design, one deep dive on the hot read path, trade-offs and a revisit trigger. Justify every component with a requirement or number; prefer PostgreSQL, Redis and the CDN.
Check before you accept
  • Redo one estimate: 120,000 ÷ 86,400 ≈ 1.4 reads/s, ~15 at peak. 500/s means someone else's numbers.
  • Every box has a sentence justifying it; "one review per stay" is a UNIQUE on booking_id.
  • The trade-offs section names something given up.
Red flags
  • Kafka, Cassandra or new microservices for ~0.01 writes per second.
  • Estimates after the architecture, or ignored by it.
  • AVG(rating) over thousands of rows on every page view.
09

Check yourself

2 min
Q1/3Recall
Put the steps of the system design method in order:
  1. 1.Clarify requirements and scope
  2. 2.Sketch the high-level design
  3. 3.State trade-offs and the 10x plan
  4. 4.Define the APIs
  5. 5.Deep-dive into the hardest parts
  6. 6.Estimate traffic, storage and bandwidth
  7. 7.Separate functional and non-functional requirements
  8. 8.Design the data model
How confident are you?

Next episode: Karim's investor deck says "ready for 10× growth", and he wants proof on one page by Friday: requests, storage, servers and cost.

Finished?

Mark it complete to update your skill map.

Go deeper (optional)You don't need this to understand the lesson. Open it when a project needs the details: Technical deep dive, Interactive simulation, Visual diagram, Analogy, Check your understanding, Practice ladder, Explain it yourself.
10

Technical deep dive

Finding the part that matters

Steps 4 and 5 deserve real artefacts, even rough ones. Writing the API and the table exposes missing requirements faster than any diagram. Here's Lina's full pass, written as the design doc she'd share with the team.

Worked example

Lina designs StayHub reviews

Maya wants guest reviews (rating, text, photos, hotel replies) on every hotel page within four weeks. StayHub: ~30,000 guests/day, ~2,500 bookings/day, 1,800 hotels, one modular monolith on PostgreSQL. Design it, and plan for Karim's 10× growth.

  1. 1Pin down the requirements

    Functional: post a review (finished stays only, one per booking), list reviews (newest, best, worst), rating summary, hotel replies, moderation. Non-functional: reviews part of the hotel page p99 < 200 ms, a submitted review is never lost, new reviews visible within minutes.

    Why: The non-functional list decides the architecture. "Visible within minutes" is what later allows a background job and a cache.

  2. 2Estimate, and say what each number changes

    Page views ~120k/day → ~1.4 reads/s avg, ~15/s at peak (×10 at 10× growth: 150/s). Reviews ~750/day → 0.01 writes/s. Text ~1 KB → ~270 MB/year. Photos ~500 KB → ~135 GB/year.

    text
    reads:  120,000 views/day ÷ 86,400 s ≈ 1.4/s → peak ×10 ≈ 15/s
    writes: 2,500 bookings × 30% = 750/day ≈ 0.01/s
    text:   750 × 1 KB × 365 ≈ 270 MB/year   → one table, no sharding
    photos: 750 × 500 KB × 365 ≈ 135 GB/year → object storage + CDN

    Why: An estimate that doesn't change the design is decoration. These rule out new databases and send photo bytes away from the app servers.

  3. 3Write the API and the table

    GET /hotels/{id}/reviews?sort=&cursor=, GET /hotels/{id}/rating-summary, POST /reviews with an idempotency key, answering 202 pending_moderation. Table: reviews(id, hotel_id, booking_id UNIQUE, rating CHECK 1–10, body, status, created_at) with an index on (hotel_id, status, created_at DESC).

    Why: booking_id UNIQUE turns "one review per stay" into a rule the database enforces. The index matches the hottest query exactly.

  4. 4Draw only the boxes the numbers ask for

    A reviews module inside the existing app, the existing PostgreSQL, a summary row per hotel cached in Redis, photos uploaded to object storage and served by the CDN, moderation as a background job fed by the outbox.

    Why: Every box points back to a requirement: Redis to the 200 ms target, the CDN to photo bandwidth, the job to "never lost" plus "within minutes".

  5. 5Deep-dive the hot read path

    Computing AVG(rating) over 3,000 reviews per page view costs ~90 ms. Instead, the moderation job updates a rating_summary row when a review is approved and deletes its Redis key. Reads hit Redis (~2 ms) or the row (~6 ms). Pagination uses a cursor on (created_at, id), not OFFSET.

    Why: This path runs on every hotel page and has an explicit target. Submission runs once every two minutes and needs no cleverness.

  6. 6Name the trade-offs and the trigger

    Summaries can lag by a few minutes. We chose PostgreSQL over a document store because "one review per booking" is a relational rule. Revisit if writes pass ~50/s, or reviews need their own deploys.

    Why: Every design gives something up. Writing the trigger down means nobody has to guess later when the design stopped fitting.

The whole thing in one line: Lina turned a vague request into numbers (0.01 writes/s, 15 reads/s at peak), let those numbers rule out Kafka and Cassandra, and spent her effort on the one hot path: a cached rating summary that keeps hotel pages under 200 ms.
StepOutputQuestion it answers
1–2 RequirementsFunctional + non-functional list, scopeWhat are we building, and how good must it be?
3 EstimatesQPS (avg/peak), storage, bandwidthHow big is it, really?
4 APIEndpoints with inputs/outputsWhat is the contract?
5 Data modelTables, keys, indexes, store choiceWhat do we store and how is it read?
6 High-level designComponent diagramWhat are the parts and how do they talk?
7 Deep dives1–3 detailed sub-designsWhere will it break first?
8 Trade-offsChoices, alternatives, 10× planWhat did we give up, and when do we revisit?
In a 45-minute interview each step gets a few minutes. In real projects, steps 1–3 often take days of conversations.
11

Interactive simulation

12

Visual diagram

Click a component

Step 6 output for the hotel reviews service. Every box is justified by a requirement or an estimate from earlier steps.

13

Analogy

  • Designing a houseHow many people live here?Designing a systemRequirements
  • Designing a houseMust stay warm at −10 °CDesigning a systemNon-functional targets
  • Designing a house5 people, 2 showers each, heater sizeDesigning a systemEstimates
  • Designing a houseDoors, windows and socketsDesigning a systemThe API
  • Designing a houseThe floor planDesigning a systemThe high-level design
  • Designing a houseStairs and foundations, checked closelyDesigning a systemDeep dives

Where the analogy breaks: a house is built once. A system keeps growing, so you also say what would change at ten times the load.

14

Check your understanding

Q1/3Reasoning
Classify each requirement for the reviews service:
Guests can sort reviews by highest rating
Review pages load in under 200 ms at p99
A submitted review is never lost
Reads stay available if one server fails
How confident are you?
15

Practice ladder

Step 1 · Trace

Follow the numbers: wishlists

Scenario Warm-up

Maya wants wishlists: guests save hotels to a list and see it on any device. A teammate's design: a separate wishlist service, a Cassandra cluster, and Kafka to sync saves between devices.

The numbers: - ~30,000 guests visit a day; about 8% save at least one hotel, 3 saves each on average. - Each saved row is ~100 bytes. The wishlist page is opened ~6,000 times a day, peaking at 10× the average in the evening. - Requirement: a save shows up on the guest's other device within a few seconds.

Trace the method's steps 3 to 6: compute writes/s, peak reads/s and storage per year, then say which of the teammate's boxes the numbers justify.

Step 2 · Complete

Finish Lina's price-alert doc

Design Practice

Guests save a hotel, dates and a target price, and get an email when the price drops below it. Lina's design doc has two empty steps. Fill in step 3 and step 7.

  1. Clarify: alert on (hotel, check-in, nights, max price). Email only. Expires after check-in.
  2. Requirements: create/list/delete alerts; notify once per drop; within 30 minutes; low cost.
  3. Estimate: ___ (StayHub has 400,000 accounts; Maya expects 10% to create alerts, 3 each. Prices for 1,800 hotels × ~90 date combinations are refreshed every 15 minutes; usually ~2% of them change.)
  4. API: POST /alerts, GET /alerts, DELETE /alerts/{id}.
  5. Data: alerts(id, guest_id, hotel_id, check_in, nights, max_price, last_notified_price), index on (hotel_id, check_in, nights, max_price).
  6. High-level: price refresh → keep only changed prices → background job matches alerts → email job.
  7. Deep dive: ___
  8. Trade-offs: up to 15 minutes of delay, well within 30; one PostgreSQL table.
Step 3 · Solve

Partner photo uploads

Design Practice

The Hotel Partners team wants hotels to upload their own photos (up to 40 per hotel, ~2 MB each from phones) and see them on the hotel page after a quick check for bad content.

StayHub has 1,800 partner hotels today; Karim's plan says 10× in two years. Hotel pages are viewed ~120,000 times a day, each showing ~8 photos.

Run the method end to end, a few lines per step. Make at least two of your estimates change a design decision.

Step 4 · Decide

"12 people are looking at this hotel"

Decision Stretch

Maya wants a live counter on hotel pages: "12 people are looking at this hotel right now". She thinks it will lift bookings.

Options on the table: - A: a WebSocket from every hotel page to new realtime servers, pushing exact counts every second. - B: each page view sets a short-lived key in Redis (viewing:{hotel}:{guest}, expires after 5 minutes); the page shows a count read on page load, refreshed every 30 s.

Facts: ~15 hotel page reads/s at peak today, ~150 at 10×. The team has no realtime servers yet. Maya says "roughly right is fine".

Write your decision as: "We chose X over Y because…, accepting the risk of…", and point to the requirement that decided it.

16

Explain it yourself

Explain the system design method to a junior engineer preparing for their first design review, and why the order of steps matters.

0 words