learn-data-engineering-with-phoebe / Builder session 4 of 10
Learn Data Engineering with Phoebe · Builder track · Session 4 of 10

File and table formats

The format you land data in is a cost and speed decision, not a detail. This session contrasts row formats (CSV, JSON - readable but fat and untyped) with columnar Parquet (compressed, typed, analytics-fast), runs live format round trips, and explains what table formats like Iceberg and Delta add on top. All of it hands-on against Daybreak.

🟠 Builder track Practitioners: DE · analysts · DS · PMs Runs in your browser · DuckDB 45 min
0-3 · Recap 3-20 · Row vs columnar & table formats 20-42 · Build-along: format round trips 42-45 · Q&A
Part 0

Where we are in the pipeline

In b2 and b3 you landed Daybreak's data - as CSV, JSON, and Parquet, without dwelling on why. Now we make that choice on purpose. This is the storage stage of the lifecycle: once data has landed, the format it sits in decides how much it costs to keep and how fast every downstream query runs. Pick well here and the transform stage (b5) flies; pick badly and everything after pays.

Live - presented in session Self-study - read after class ▶ Live pipeline - editable & runnable Official sources covered
★ What you walk out with today A gut sense of when to reach for CSV, JSON, or Parquet, hands-on proof that Parquet keeps types while CSV loses them, why reading two columns from Parquet is nearly free, and a clear map of what table formats (Iceberg, Delta, Hudi) add on top of plain Parquet files - the seam into the warehouse course.
Part 1 · covers DLAI Storage & Queries M1

Row formats vs columnar 6 min live

Formats split into two families. Row formats (CSV, JSON) store one record at a time, as human-readable text - easy to eyeball, but uncompressed, untyped, and fat. Columnar formats (Parquet) store one column at a time, as compressed typed binary - unreadable by eye, but small and blisteringly fast for the column-slicing that analytics does all day.

Readability File size Scan speed CSV row · text high - eyeball it large, no compress slow, reads all JSON row · nested high - but verbose largest, repeats keys slow, parse-heavy Parquet columnar · binary low - binary small, compressed fast, reads needed cols Format is a cost + speed decision: readable for humans, or small + fast for machines.
🔍 Click to zoom - CSV and JSON win on readability, Parquet wins on size and analytics speed
LiveWhy columnar is fast for analytics3 min

The whole difference is layout on disk. Analytics rarely wants whole rows - it wants "sum this one column across a billion records". Layout decides how much work that takes:

  • Row format (CSV/JSON): values are interleaved record by record. To sum one column, the engine must read every byte of every row and skip the rest. All that I/O, mostly wasted.
  • Columnar (Parquet): each column is stored together. To sum one column, the engine reads only that column's block and ignores the rest entirely. Less I/O, and similar values sit together so they compress hard.
  • Types are baked in: Parquet records that a column is a DATE or an INTEGER. CSV is all text, so every read re-parses and re-guesses types - slow and error-prone.
Real world

Why the lake is full of Parquet. When a dashboard query touches 3 columns of a 200-column table, columnar storage reads roughly 3/200ths of the bytes; row storage reads all of it. On big tables that is the difference between a query that returns in a second and one that grinds for minutes - which is why analytical lakes standardised on Parquet.

Self-studyWhen readable formats still win2 min read

Parquet is not always right. CSV and JSON keep earning their place at the edges of a pipeline. CSV is the universal interchange format - every tool, every partner, every spreadsheet reads it, so it is often what data arrives as and what a non-technical stakeholder wants exported. JSON is the right shape for nested, semi-structured, still-changing data from APIs. The rule: readable formats for the human-facing and fluid edges, Parquet for the machine-facing analytical core.

Part 2 · hands-on round trips

Format round trips 6 min live

The fastest way to feel the difference is to write the same data three ways and read it back. Notice what survives the trip: Parquet remembers that order_date is a DATE, while CSV hands it back as plain text you have to re-parse. That lost type is the hidden tax of row formats.

LiveOne table, three formats3 min

Write orders to CSV, JSON, and Parquet, then read one column back from each and check its stored type. Press ▶ Run (first run caches the ~8 MB engine).

-- land the same data three ways
COPY (SELECT * FROM orders) TO 'f/orders.csv'     (FORMAT CSV, HEADER);
COPY (SELECT * FROM orders) TO 'f/orders.json'    (FORMAT JSON);
COPY (SELECT * FROM orders) TO 'f/orders.parquet' (FORMAT PARQUET);

-- read one value back from each and see what type it is
(SELECT 'parquet' AS fmt, typeof(order_date) AS date_type
 FROM 'f/orders.parquet' LIMIT 1)
UNION ALL
(SELECT 'csv', typeof(order_date)
 FROM read_csv_auto('f/orders.csv') LIMIT 1);
LiveReading a subset from Parquet is cheap3 min

Because Parquet stores columns separately, reading two of them touches only those two columns' data - the engine never loads the rest. Land the full table, then pull just two columns back.

COPY (SELECT * FROM orders) TO 'f/orders.parquet' (FORMAT PARQUET);

-- only these two columns' data is read from the file
SELECT order_id, order_date
FROM 'f/orders.parquet'
LIMIT 8;
Part 3 · table formats

Table formats: Iceberg, Delta, Hudi 5 min live

Parquet is a file. A pile of Parquet files is not a table - you cannot safely update it, evolve its schema, or ask "what did this look like last Tuesday". Table formats - Iceberg, Delta Lake, Hudi - add a metadata layer on top of Parquet files that brings database powers to the lake. This is what turns a data lake into a "lakehouse".

Self-studyWhat table formats add on top of Parquet3 min read

A table format keeps a manifest of which Parquet files make up the table right now, plus a log of changes. That metadata unlocks three things plain files cannot do:

ACID transactions schema evolution time travel TABLE FORMAT (LAKEHOUSE) adds a manifest over Parquet files PARQUET FILES fast, columnar, no transactions = a table you can trust A pile of Parquet files is not a table - you cannot update it safely or replay yesterday's version.
🔍 Click to zoom - a table format adds a manifest, not more storage
  • Transactions (ACID): a write either fully lands or not at all. No more readers catching a half-written table mid-update.
  • Schema evolution: add, rename, or drop a column without rewriting every file. The metadata tracks the change.
  • Time travel: query the table as it was at an earlier version or timestamp - for audits, debugging, and reproducibility.
Honest note - not live here DuckDB reads plain Parquet natively, which is why every demo on this page runs. Iceberg, Delta, and Hudi need extra extensions or engines (Spark, Trino, Flink) and are not live in this browser. We cover what they do; the deep dive on lakehouse table design lives in learn-data-warehouse (b9 there).
Demo 1 of 2

The format bake-off ★ 6 min · everyone builds

Land the same orders to CSV and Parquet, read both back, and put them head to head. The row counts match exactly - same data - but the stored type of the date column does not: Parquet keeps DATE, CSV hands back text that needs re-parsing. That single difference is why the analytical core runs on Parquet.

Land twice: write orders to a CSV and a Parquet file from the same source query.

Count both: confirm the row counts are identical - no data lost either way.

Compare types: read the date column's type from each. Parquet = DATE, CSV = a re-parsed value.

-- land the same data as CSV and Parquet
COPY (SELECT * FROM orders) TO 'bo/orders.csv'     (FORMAT CSV, HEADER);
COPY (SELECT * FROM orders) TO 'bo/orders.parquet' (FORMAT PARQUET);

-- counts match, but the stored date type does not
SELECT 'parquet' AS fmt,
       (SELECT count(*) FROM 'bo/orders.parquet')            AS rows,
       (SELECT typeof(order_date) FROM 'bo/orders.parquet' LIMIT 1) AS date_type
UNION ALL
SELECT 'csv',
       (SELECT count(*) FROM read_csv_auto('bo/orders.csv')),
       (SELECT typeof(order_date) FROM read_csv_auto('bo/orders.csv') LIMIT 1);
Real world

Lost types cause silent bugs. A pipeline that lands to CSV and re-reads it often re-guesses a date as a string or a zip code as a number. Downstream a sort goes alphabetical instead of chronological, or leading zeros vanish. Parquet's baked-in types remove that whole class of bug - the data comes back exactly as it went in, which is why it is the default for anything a machine will read again.

✗ CSV loses the type order_date comes back as text needs re-parsing every read sorts alphabetically, not by date ✓ Parquet keeps the type order_date stays a DATE no re-parsing on read sorts correctly, chronologically Lost types cause silent bugs - a sort goes alphabetical instead of chronological, unnoticed.
🔍 Click to zoom - the same round trip, one format remembers the type
Demo 2 of 2

Your turn: round-trip and verify ★ 8 min · build your own

Each editor starts fresh from Daybreak's raw source. Write to a format, read it back, and verify the trip was clean. All three run against the source tables.

LiveQ1 · Round-trip order_items through Parquet3 min

Write order_items to Parquet, read it back, and verify the row count matches the source - a clean round trip loses nothing.

COPY (SELECT * FROM order_items) TO 'rt/items.parquet' (FORMAT PARQUET);

SELECT (SELECT count(*) FROM 'rt/items.parquet') AS file_rows,
       (SELECT count(*) FROM order_items)        AS source_rows;
LiveQ2 · Land a filtered query, then aggregate4 min

COPY only completed orders to Parquet, read them back, and aggregate - count orders per channel straight from the landed file.

COPY (SELECT * FROM orders WHERE status = 'completed')
TO 'rt/completed.parquet' (FORMAT PARQUET);

SELECT channel, count(*) AS completed_orders
FROM 'rt/completed.parquet'
GROUP BY channel
ORDER BY completed_orders DESC;
Self-studyQ3 · When is JSON the right choice?3 min

A thought exercise, no SQL. Parquet wins for the analytical core - so when would you deliberately keep data as JSON?

  • Nested, semi-structured source data: an API returns orders with a variable list of line items and optional metadata. JSON stores that nesting natively; forcing it into flat columns too early loses shape.
  • Schema still fluid: when the source keeps adding and dropping fields, JSON absorbs the change without a rewrite. You land raw JSON, then flatten to Parquet once the schema settles.
  • The pattern: JSON at the raw landing edge for messy, changing sources; Parquet for the cleaned, stable, analytics-facing tables. Right tool, right stage.
Homework

Try it yourself - this week ◐ 20-30 min total

Source material

Official sources covered

This session teaches the working 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:

DLAI Storage & Queries - M1: File formats, row vs columnarParts 1-2 · CSV/JSON vs Parquet, why format is a cost decision
Fundamentals of Data Engineering (Reis & Housley) - storage chapterParts 1-2 · serialization, compression, columnar storage
DLAI Storage & Queries - M2: Lakehouse & table formatsPart 3 · Iceberg/Delta concept; depth is the warehouse course
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · What is the core trade-off between Parquet and CSV?

CSV is readable text but uncompressed and typeless. Parquet is columnar binary - small, type-preserving, and fast for the column-slicing analytics does, at the cost of not being eyeball-readable.

2 · What do table formats (Iceberg, Delta, Hudi) add on top of Parquet files?

A table format adds a metadata layer over Parquet files, bringing ACID transactions, schema evolution, and time travel - turning a pile of files into a lakehouse table.

3 · When should you keep source data as JSON rather than convert to Parquet?

JSON natively holds nesting and absorbs schema change, so it fits messy, evolving API sources at the raw edge. Flatten to Parquet once the shape stabilises for the analytical core.

Builder session 4 cheat sheet · pin this

Row formatsCSV, JSON. Human-readable text, uncompressed, untyped, fat. Great for interchange and the edges.
ColumnarParquet. Compressed typed binary, stored by column. Small and fast for analytical scans.
Format = cost + speedReadable for humans, or small + fast for machines. It is a real decision, not a detail.
Parquet keeps typesDATE stays DATE. CSV hands everything back as text you must re-parse - a silent-bug source.
Columnar reads are cheapSelecting 2 of 200 columns reads only those 2 columns' blocks. That is the speed win.
Table formatsIceberg/Delta/Hudi = metadata on top of Parquet: ACID, schema evolution, time travel. The lakehouse.
Not live hereDuckDB reads Parquet natively; Iceberg/Delta need extensions/engines. Depth in the warehouse course.
Running projectDaybreak's core lands in Parquet, JSON at messy edges. Next: b5, batch transformation.