Where the pipeline stands
Daybreak's pipeline now extracts (b2), ingests incrementally (b3), lands in good formats (b4), transforms in the engine (b5), and can even handle a stream (b6). The data is correct and well-shaped. This session asks a different question: how is it physically laid out on storage? Get that wrong and every query pays for it - scanning terabytes it did not need, on storage that costs more than it should. Storage engineering is the physical-layout craft that makes correct data also fast and cheap.
Physical layout: partition so queries skip 6 min live
The single biggest physical-layout decision is partitioning: splitting a dataset into separate files by the value of a column - most often a date. Then a query filtered to one month only opens that month's files and skips the rest. This is partition pruning, and it is the difference between scanning all your data and scanning a sliver of it. The art is picking a partition column that matches how queries actually filter.
LivePartitioning, and the two ways to get it wrong3 min▶
Partitioning writes each group of rows to its own file (or folder of files) keyed by a column. A query that filters on that column reads only the matching partitions - it prunes the rest. Pick the column queries filter on most, and pick a granularity that keeps partitions a sensible size.
- One big file is bad: every query, however selective, must scan the whole thing. No pruning is possible - the engine cannot skip what is not separated.
- A million tiny files is worse: over-partition (say, by the second) and you drown in file overhead - each file has a cost to open and read metadata for. The engine spends more time managing files than reading data.
- The sweet spot: partitions large enough to be worth opening, small enough that a typical query skips most of them. Date partitions (day or month) hit this for most time-series data.
Partitioning is often the single cheapest speedup. A team's dashboard query scanned a whole year of event data every load - slow and, on a pay-per-scan warehouse, expensive. Partitioning the table by day meant the "last 7 days" dashboard read seven partitions instead of 365. No new hardware, no rewrite of the query - just laying the same bytes out to match how they were queried. Cost and latency both dropped by more than an order of magnitude.
Self-studyFile layout: not too few, not too many2 min read▶
Beyond which column, file layout matters. Columnar formats like Parquet (b4) store row groups you can skip within a file; partitioning skips whole files between them. The two compound. The failure modes are symmetric: too few files (one giant blob) means no skipping at all; too many files (the "small files problem") means the engine pays per-file overhead that swamps the read. Healthy targets are partitions in the tens-to-hundreds-of-megabytes range - big enough to amortize opening, small enough to prune usefully. Compaction jobs exist precisely to merge small files back up when a streaming or frequent-append pipeline has fragmented a table.
Partition 300k orders, then query one 5 min live
This playground loads big_orders - 300,000 synthetic Daybreak orders, generated in-browser, nothing shipped over a network. You will write them out partitioned by a derived month, then read a single month back and see that only that partition's rows are touched. This is partition pruning on data big enough to feel it.
LiveWrite partitioned, then read one partition4 min▶
Partitioning means splitting a table into separate files by a column, so a query that filters on that column reads only the files it needs. The simplest form is one file per partition: write each month to its own Parquet file, then a month-query reads just that one file. Press ▶ Run - note the row count is one month's slice, not all 300k.
-- hand-partition: write January's slice to its own file
COPY (
SELECT * FROM big_orders
WHERE strftime(order_date, '%Y-%m') = '2026-01'
) TO 'orders_2026_01.parquet' (FORMAT PARQUET);
-- read ONE partition file - only January's rows are scanned
SELECT count(*) AS rows_scanned,
ROUND(avg(quantity * unit_price),2) AS avg_line_value
FROM 'orders_2026_01.parquet';
Self-studyStorage tiers: hot, warm, cold2 min read▶
Not all data deserves the same storage. Tiering trades latency for cost:
- Hot: queried constantly, must be instant. Kept on the fastest, most expensive storage (SSD-backed, standard object storage). Daybreak's last-90-days orders.
- Warm: queried occasionally. Cheaper storage, slightly slower retrieval. Last year's orders you still report on monthly.
- Cold: rarely touched, kept for compliance or the odd deep dive. Cheapest storage (S3 Glacier and the like), retrieval measured in minutes to hours. Daybreak's five-year-old records.
The base of all three is object storage (Amazon S3, GCS, Azure Blob) - cheap, effectively infinite, and where lifecycle rules automatically move data hot → warm → cold as it ages. You engineer the tiering; the cloud enforces it.
Object storage and separated compute 4 min live
The modern stack made one decisive move: store data on cheap object storage and keep compute separate, spinning it up only when you query. This is why a Parquet file on your laptop (b4) and a Parquet object in S3 are the same thing - the engine reads either the same way. Storage scales independently of compute, so you pay for a petabyte of storage without paying for a petabyte of always-on compute.
Self-studyDistributed storage, in one idea (read-only)3 min read▶
Behind an S3 bucket is a distributed file system: your "file" is split into pieces, replicated across many machines for durability, and addressed as one logical object. You never see the machines - you just read a path. That abstraction is what lets a single SELECT ... FROM 's3://daybreak/orders/*.parquet' transparently read data spread across a fleet. The read below is exactly what your playground does locally; only the path changes when it points at the cloud:
Partition, then prove the prune with a timer ★ 11 min · everyone builds
Write the 300k orders partitioned by month, then run two reads back to back: one hitting a single month, one scanning every partition. Compare the "ms in your browser" figures - the pruned query touches a fraction of the data.
Write: partition big_orders into one folder per month.
Prune: read a single month - the engine opens just that partition.
Compare: read every partition and watch the row count (and time) jump - that is the scan you avoided.
-- hand-partition: write one month to its own file
COPY (
SELECT * FROM big_orders
WHERE strftime(order_date, '%Y-%m') = '2026-01'
) TO 'orders_2026_01.parquet' (FORMAT PARQUET);
-- PRUNED read (one partition file) vs FULL scan (the whole table)
SELECT (SELECT count(*) FROM 'orders_2026_01.parquet') AS pruned_read,
count(*) AS full_scan_read
FROM big_orders;
The number you just saw is the whole point of storage engineering. On a pay-per-scan cloud warehouse, that pruned read is not just faster - it is cheaper by the same ratio, because you are billed for bytes scanned. Teams cut warehouse bills dramatically by nothing more than partitioning tables to match their query filters. The data did not change; its physical layout did.
Your turn: partition differently, count, and reflect ★ 10 min · build your own
Q1 partitions by a different column. Q2 counts rows per partition to check the layout is balanced. Q3 is the trap every storage engineer must learn to avoid.
LiveQ1 · Partition by channel instead, then read one3 min▶
COPY (SELECT * FROM big_orders WHERE channel = 'web') TO 'orders_web.parquet' (FORMAT PARQUET); SELECT count(*) AS web_orders FROM 'orders_web.parquet';
LiveQ2 · Count rows per partition4 min▶
-- how many rows each monthly partition would hold
SELECT strftime(order_date, '%Y-%m') AS month,
count(*) AS rows_in_partition
FROM big_orders
GROUP BY month
ORDER BY month;
Self-studyQ3 · The over-partitioning trap3 min▶
No SQL - reason about the failure mode. Partitioning helps by letting queries skip files, so it is tempting to partition on everything: month and channel and customer and product. Do that and you shatter 300k rows into thousands of tiny files, each with a handful of rows. Now every query, even a pruned one, must open, read metadata for, and stitch together a huge number of files - and the per-file overhead swamps any saving from skipping. This is the small files problem, and at real scale it is a classic performance killer: a table "optimized" into millions of kilobyte files runs slower than the single blob it started as. The rule: partition on the one or two columns queries actually filter on, at a granularity that keeps each partition a healthy size (tens to hundreds of MB). More partitions is not more speed - past a point it is the opposite. When a streaming or frequent-append job has already fragmented a table, a compaction step merges the shards back up.
Try it yourself - this week ◐ 20-30 min total
- Take Demo 1 and partition
big_ordersbystatusinstead of month. Read thecompletedpartition and compare its row count to a full scan. - Run Q2 and look at the row counts per partition. Are they balanced, or is one month far larger? Unbalanced partitions hurt pruning - why?
- Pick one large table you query at work. What column do your queries filter on most? That is almost certainly your partition key. Is the table partitioned on it today?
- Map one dataset you own onto hot / warm / cold. What could move to cheaper storage without anyone noticing the latency?
Official sources covered
This session teaches the physical-storage core of the DeepLearning.AI Data Engineering Professional Certificate (Joe Reis) and Reis & Housley's Fundamentals of Data Engineering, run on a live engine instead of slides. This page covers:
Three questions before you go 🎯 ◐ 90 seconds
1 · What does partitioning a table by date save you?
Partitioning splits data into files by a column so a query filtered on that column prunes to the matching partitions and skips the rest. Less data scanned means faster queries and, on pay-per-scan warehouses, a directly lower bill.
2 · What is the difference between a hot and a cold storage tier?
Tiers trade latency for cost. Hot data is on fast, expensive storage for instant access; cold data (e.g. S3 Glacier) is cheapest but takes minutes to hours to retrieve. You engineer the tiering; cloud lifecycle rules move data hot → warm → cold as it ages.
3 · Why can over-partitioning hurt performance?
Partitioning on too many columns or too fine a grain creates the "small files problem" - the engine spends more time opening and reading metadata for countless tiny files than it saves by pruning. Partition on the one or two columns queries filter on, at a healthy per-partition size.