learn-system-design-with-phoebe / Session 3 of 8
Learn System Design with Phoebe · Session 3 of 8

Storage and serving

This is the session where the course stops being comfortable. Every decision that makes Daybreak's dashboard fast makes Daybreak's data older, and there is no configuration of anything that escapes that. Tonight you learn to read the freshness-cost curve, to pick a store from the access pattern rather than from the conference talk, and to say out loud which meter you are spending. The naive architecture you are replacing has the freshest data you will ever have. That is not a joke.

🟡 Medium Anyone choosing between a database, a warehouse and a stream No cloud account needed 45 min
0-3 · Welcome 3-18 · Access patterns, the curve, and the tail 18-40 · Build-along: Daybreak's read path, decided 40-45 · Q&A
Part 0

Where we are

Sessions 1 and 2 were about what the system must do and who owns which piece. Tonight is the first session where you spend a meter to buy another one, on purpose, in writing.

🎫 Clear all three quiz questions to stamp your passport Live = presented in the 45 minutes Self-study = the full depth, after class

★ What you walk out with tonight. Daybreak's storage decision written down with its consequence attached, the ability to say where 9.4 seconds of dashboard load actually goes, and a working answer to the question "should this be streaming" that does not depend on anyone's opinion.

LiveThe sentence this whole session hangs on2 min▶

Daybreak's current architecture has the best data freshness it will ever have. The dashboard queries production. The number on screen is five minutes old. Everything you are about to do makes that worse.

That is not a paradox and it is not an argument for doing nothing. It is the shape of the trade, and the reason it surprises people is that "modernise the platform" is usually sold as an improvement on every axis at once. It is not. It is an improvement on latency, cost-at-scale, correctness and blast radius, paid for in freshness.

If you take nothing else from tonight: name what gets worse, in the document, before somebody else names it in the review.

Part 1 · covers access patterns

The store is chosen by how it is read, not by what it holds

6 min live

Row stores and column stores are not competing products at different points on a quality ladder. They are answers to two different questions, and the question is asked by your read pattern.

Row store · reads whole records Column store · reads whole columns order 8412 · all 24 fields order 8413 order 8414 order 8415 sku email total addr "Show me this customer's order" reads one row. "Revenue by SKU, 2 years" reads 2 of 24 columns. The arithmetic nobody does Daybreak's dashboard needs 2 of 24 columns. A row store still reads all 24. That is 12x the bytes, before compression, for every single scan. Hence x0.30.
🔍 Click to zoom - the column store is not faster in general, it is faster at this question
LiveFour stores, and the one question each is the answer to4 min▶
StoreAnswersCosts you
Row store (OLTP)"Give me this one record, and let me write it safely"Wide scans, which it does by reading everything
Column store (OLAP)"Aggregate these two fields across two years"Single-record writes and updates, which it hates
Object storage / lake"Keep everything cheaply, including things I do not have a use for yet"Structure, guarantees, and the ability to delete one person's data
Stream / log"Tell me the moment it happens, and let several consumers see it independently"Money, operational surface, and simple mental models

The correct Daybreak answer is row store and column store, which is also the correct answer for almost every company of its size. The interesting decision is not which one - it is what connects them, and how often.

Watch for the word "lakehouse" doing work in a room. It is a real and useful shape - table format and transactions over object storage - but it is frequently deployed as a way to avoid making the batch-versus-stream decision, which is the decision that actually costs money.

Browser 120 ms render API 80 ms serialise Full table scan 7,900 ms 24 columns, 2 years Lock waits 1,300 ms checkout is writing Total p95: 9,400 ms. The two boxes on the right are 98 percent of it. A faster browser, a faster API and a bigger box all attack the 200 ms.
Part 2 · covers the curve

The freshness-cost curve, and where to stand on it

7 min live

Batch, micro-batch and streaming are not three levels of sophistication. They are three points on a curve where freshness is bought with money and operational surface, and the curve gets steep at the end.

fresher data, to the right cost Nightly 24 hr · $420 Micro-batch 18 min · +$180 Near-real-time 5 min · +$400 Streaming 1 min · +$900 The first 23 hours of freshness cost $180. The last 17 minutes cost $900. Nobody argues about the cheap part of this curve. Every argument is about the right-hand end.
🔍 Click to zoom - freshness is cheap until it is not, and the elbow is where the design conversation belongs

Dashboards query production directly. Data is 5 minutes old.

p95 is 9.4 seconds. Analytics queues behind checkout. One upstream change reaches 82 percent of everything downstream. It costs $1,180 a month and it is, genuinely, the freshest Daybreak will ever be. For a founder checking today's orders on a phone, this is not the worst design in the world.

Replica, column store, 15-minute incremental loads, pre-aggregated marts.

p95 is 543 ms - seventeen times faster. Data is 24 minutes old instead of 5. Cost is $2,180 a month. For a dashboard four people open at 08:40 to compare against yesterday, the 19 extra minutes of staleness are worth precisely nothing to anybody, and the 8.9 seconds are worth a great deal.

LiveThe three questions that settle batch versus streaming4 min▶
  • Is there a decision on the other end that changes within the window? A pager that fires on checkout failures: yes, absolutely. A revenue dashboard compared against yesterday: no, and no amount of enthusiasm changes that.
  • Who is awake to act on it? Minute-fresh data arriving at 03:00 with nobody on call is a cost with no benefit attached. Freshness only has value where there is someone to act, and a decision they can take.
  • What is the operational surface you are adding? Streaming adds components, and components are blast radius. On the session 5 board, streaming moves blast radius up by 12 points. That is not a criticism of streaming, it is its price.
The honest version

Streaming is correct for Daybreak's fraud alerting and incorrect for Daybreak's revenue dashboard, and both of those systems live in the same company, on the same data, next to each other. "Should we be streaming" is not a question about the company. It is a question about one use case at a time.

Self-studyPACELC: the version of CAP that survives contact with a real system4 min▶

Brewer's CAP says that during a network partition you choose between consistency and availability. True, and less useful than it looks, because partitions are rare and the theorem says nothing about the other 99.9 percent of the time.

Abadi's PACELC completes it: if there is a Partition, choose Availability or Consistency; Else, choose Latency or Consistency. The second half is the half you live in. Every replica you add, every cache you put in front of a table, every materialized aggregate is you choosing latency over consistency on an ordinary Tuesday with no partition anywhere.

Which is exactly what the read replica on Daybreak's board is. It is two minutes of replication lag, traded for 4,230 milliseconds. Say it that way in a design review and nobody has to look up a theorem.

Build-along

Decide Daybreak's read path, and write the consequence

★ 22 min · everyone builds

Five moves down the read path, in the order a real team should do them, which is not the order teams usually do them in.

Read replica · $260 · p95 9,400 to 5,170 ms. Stop analytics competing with checkout. Two minutes of replication lag, 45 percent of the latency gone, and it is a weekend of work. Almost nobody does this first and almost everybody should.

Columnar warehouse, nightly · +$420 · 5,170 to 1,551 ms. Read 2 columns instead of 24. This is also the moment your data becomes a day old, and you should feel that.

Incremental every 15 min · +$180 · data 1,440 min to 18 min. Buy back almost all the freshness you just spent, for the second-cheapest line on the bill. Needs idempotent loads, which is session 4.

Pre-aggregated marts · +$140 · 1,551 to 543 ms. Compute once on write instead of every time on read. The largest single latency win left, for the price of a lunch.

Now write the consequence. p95 543 ms, cost $2,180, data 24 minutes old instead of 5. You made the data 19 minutes worse. Put that sentence in the doc yourself.

Find your 7.9 seconds. On a real query you care about, get the actual breakdown - scan, lock wait, serialise, render. Do this before choosing anything. Half the time the answer is embarrassing and free.

Ask whether the read pattern is wide or narrow. Count the columns your heaviest query needs against the columns in the table. If it is under a fifth, you have a column-store argument with a number attached.

Pick a load rhythm by asking the decision question, not the technology question. "What decision changes if this arrives 15 minutes sooner?" Write the answer down verbatim. If it is "none", you have just saved $900 a month.

Write the consequence sentence. "We are trading X minutes of freshness for Y milliseconds of p95 and Z dollars." If you cannot fill in all three letters, you have not finished deciding.

Draft the option you are rejecting, fairly. Write the two-sentence case for streaming Daybreak's whole path. Make it good. Then write the one sentence that beats it - which is almost certainly about who is awake at 03:00 to act on it.

Sanity-check against the tail. Your p95 is the number in the requirement. If your evidence is an average, go and get the percentile, because the two can differ by a factor of five on a system with lock contention.

If you take one thing from tonight. Get the breakdown before you choose the store. Nearly every over-engineered platform started with someone choosing an architecture to fix a number they had never decomposed.

Homework

Before session 4

25 min
  1. Decompose one slow thing. Get the real stage-by-stage timing of a query, a page or a report you care about. Write the four numbers down.
  2. Count columns. For your heaviest analytical query: columns needed against columns in the table. That ratio is your column-store argument or the end of it.
  3. Ask the decision question to a real person. "What would you do differently if this were 15 minutes fresher?" Bring their exact words to session 4.
  4. Write the consequence line into your design doc. Freshness traded, latency bought, dollars spent. Three numbers, one sentence.
  5. Find one thing on the right-hand end of the curve in your own stack. Something streaming, near-real-time or event-driven that nobody acts on within its window. It is nearly always there, and it is nearly always someone's old enthusiasm.
Check yourself

Three questions

Clear all three and session 3 gets stamped in your passport.

1 · Daybreak moves its dashboard from production to a columnar warehouse loaded nightly. What happened to data freshness?

The naive architecture is the freshest one, because it is production. A nightly load makes the data as old as the load interval. Every serious architecture on the board is worse on freshness than doing nothing, and the honest doc says so.

2 · Which question best settles whether something should be streaming?

Volume is not the trigger - a low-volume fraud alert needs minutes, a huge revenue rollup does not. Freshness only has value where a decision changes and a person or a system can act. Otherwise you have bought a cost with no benefit and 12 extra points of blast radius.

3 · PACELC adds what to CAP that matters day to day?

CAP describes the rare case. The "ELC" half describes the ordinary Tuesday: Else, choose Latency or Consistency. Daybreak's read replica is exactly that trade - two minutes of lag for 4,230 milliseconds - and naming it that way beats citing a theorem.

Sources covered

What this session teaches, and where it came from

✓OLTP, OLAP, lake and stream chosen by access patternRow versus column shown as bytes read, with Daybreak's 2-of-24-columns ratio as the argument
✓The freshness-cost curveNightly, micro-batch, near-real-time and streaming as points on one curve. The first 23 hours cost $180, the last 17 minutes cost $900
✓Replicas and materialized aggregatesThe two cheapest latency levers on the board, with the lock-contention breakdown that justifies the replica
✓PACELC (Abadi), extending CAP (Brewer)The Else-Latency-or-Consistency half, applied directly to the replica decision
✓The Tail at Scale, Dean and BarrosoDesign to p95, and check that your evidence is a percentile rather than a mean
◐Partitioning, file formats, physical modelingTreated as decisions with consequences. The hands-on build lives in learn-data-warehouse-with-phoebe and learn-data-engineering-with-phoebe

Every fact on this page, and its verification tier, is recorded in the course's source map. Certificates, videos and assessments stay with the official providers.

Session 3 cheat sheet · pin this

Access pattern decidesNarrow reads want rows. Wide scans want columns. Count the columns and the argument is over.
Decompose before choosing7,900 ms of scan and 1,300 ms of lock wait. The browser was 120. Find the number before spending the week.
The curve has an elbow23 hours of freshness for $180. The last 17 minutes for $900. Every argument is about the elbow.
The streaming testDoes a decision change in the window, and is someone awake to take it? Volume is not the trigger.
PACELCElse, Latency or Consistency. Every replica and cache is that trade, on an ordinary day.
Name what got worse"24 minutes old instead of 5." Write it yourself, before the reviewer writes it for you.