Where we are in the pipeline
In b1 you ran the whole lifecycle once - generation, ingestion, storage, transformation, serving. Now we zoom in on the very first seam: generation → ingestion. Data is born in source systems Daybreak's app team owns; your job is to connect to each one and copy data out cleanly, without ever changing the source. Get this wrong and every stage downstream inherits the mess.
The source zoo 6 min live
"Source system" is any place data is generated before it reaches you. There is not one kind - there is a zoo, and each animal is read a different way. A data engineer's first skill is recognising which shape is in front of them, because the read pattern changes completely from one to the next.
LiveWhat each shape means for how you read it3 min▶
The shape of the source decides the read pattern. There is no universal connector - part of the craft is matching tool to shape.
- Relational (Daybreak's tables): query with SQL. A
SELECTis your extract. Structured, typed, easy - the friendliest source. - Files (CSV / JSON): someone drops an export in a folder. You read the file, not a database. Formats vary wildly.
- Object storage (S3 / GCS): files at cloud scale. Same idea as local files, but you point a reader at a bucket path.
- API: you call an endpoint over HTTP and get JSON back. Rate limits and pagination make this its own skill (card below).
- Logs: append-only text lines, semi-structured, huge volume. Often parsed and batched.
- Streams: unbounded events arriving continuously - a different mindset entirely, covered in b6.
You extract FROM sources, you don't change them. A source system belongs to another team and often runs the live product. Writing to it, locking its tables, or hammering it with heavy queries can take the app down. The golden rule: read what you need, land your own copy, and do all your work on the copy.
Self-studyWhy the source zoo exists at all2 min read▶
Each source shape was built for its own job, not for you. An app database is tuned for fast single-row writes, not big analytical scans. An API is built to serve a mobile screen, not to dump a million rows. Logs are optimised for cheap appends. None of them were designed to feed a warehouse - which is exactly why ingestion (b3) and storage (b4-b7) exist: to translate whatever the source hands you into something analytics can use.
Extracting from a relational source 6 min live
Daybreak's core data lives in a relational database, the friendliest source shape. Your extract is a SELECT. The one decision that matters early: do you drag the whole table, or only the columns you actually need? Column discipline is free performance and it starts at extraction.
LiveExtract a trimmed projection of orders3 min▶
This is your extract from a relational source: a SELECT that names only the columns the pipeline needs, not SELECT *. Press ▶ Run (first run downloads the ~8 MB engine once, then cached).
SELECT order_id, order_date, channel FROM orders WHERE status = 'completed' LIMIT 8;
LiveReading files - the "someone dropped a file" pattern3 min▶
Half your sources arrive as files, not databases. We can simulate that exactly: write orders out to a CSV in the browser's virtual file system, then read it back as if a partner team had dropped it in a folder. The extract is now a file reader, not a SQL query against the source.
COPY (SELECT * FROM orders) TO 'drop/orders.csv' (FORMAT CSV, HEADER);
SELECT order_id, order_date, status
FROM read_csv_auto('drop/orders.csv')
LIMIT 5;
Reading JSON, the API shape 5 min live
APIs almost always hand back JSON - a semi-structured shape with nested objects and arrays, not neat rows and columns. You cannot browse a browser out to an external API here, but you can practise the exact read: write Daybreak data to JSON, then read it back and watch DuckDB flatten it into a table.
LiveRound-trip customers through JSON3 min▶
Write customers to a JSON file, then read it with read_json_auto. An API response looks just like this file's contents - DuckDB infers the columns and flattens the structure into rows for you.
COPY (SELECT * FROM customers) TO 'api/customers.json' (FORMAT JSON);
SELECT customer_id, name, city, plan
FROM read_json_auto('api/customers.json')
LIMIT 5;
Self-studyAPIs in the wild - rate limits & pagination3 min read▶
The browser cannot call external APIs, so this part is concept, honestly labelled. When you extract from a real API three things bite you that a database never does:
- Rate limits: the API caps how many calls you can make per minute. Blow past it and you get throttled or blocked. You add backoff and retry logic.
- Pagination: the API returns 100 rows at a time with a "next page" token. A full extract means looping until the token runs out - not one query.
- Nested JSON: a single response often nests orders inside customers inside a wrapper. You flatten it after landing, not during the call.
Ingest three source shapes ★ 6 min · everyone builds
One script, three source shapes landed to three formats: orders to Parquet, customers to CSV, products to JSON. Then read all three back and prove each one landed with a single count summary. This is a miniature multi-source ingestion.
Extract + land: COPY three source tables out to three files, one per format.
Read back: point a reader at each landed file - Parquet path, read_csv_auto, read_json_auto.
Prove it: a UNION-style summary counts rows in each file, showing all three sources are safely yours.
-- extract + land three source shapes into three formats
COPY (SELECT * FROM orders) TO 'land/orders.parquet' (FORMAT PARQUET);
COPY (SELECT * FROM customers) TO 'land/customers.csv' (FORMAT CSV, HEADER);
COPY (SELECT * FROM products) TO 'land/products.json' (FORMAT JSON);
-- read each back and prove it landed
SELECT 'orders.parquet' AS landed_file, count(*) AS rows
FROM 'land/orders.parquet'
UNION ALL
SELECT 'customers.csv', count(*) FROM read_csv_auto('land/customers.csv')
UNION ALL
SELECT 'products.json', count(*) FROM read_json_auto('land/products.json')
ORDER BY landed_file;
This is what an ingestion job actually does. Swap the local files for S3 buckets and the tables for a database plus an API plus a log stream, and this exact pattern - extract each source to its own landed file, then confirm counts - is running nightly at real companies. The confirm-counts step is the difference between "the job ran" and "the job worked".
Your turn: land your own sources ★ 8 min · build your own
Each editor starts fresh from Daybreak's raw source. Write the extract, run it, read the count, fix if it is wrong. All three run against the source tables.
LiveQ1 · Extract completed orders to Parquet3 min▶
Extract only completed orders' id, date, and channel to a Parquet file, then read it back to confirm the count.
COPY (SELECT order_id, order_date, channel
FROM orders
WHERE status = 'completed')
TO 'out/completed.parquet' (FORMAT PARQUET);
SELECT count(*) AS completed_rows FROM 'out/completed.parquet';
LiveQ2 · Land order_items to CSV, then sum revenue4 min▶
COPY order_items to a CSV, read it back with read_csv_auto, and sum quantity times unit price to get total revenue - all from the landed file, not the source.
COPY (SELECT * FROM order_items) TO 'out/items.csv' (FORMAT CSV, HEADER);
SELECT ROUND(SUM(quantity * unit_price), 2) AS revenue
FROM read_csv_auto('out/items.csv');
Self-studyQ3 · Why extract to a file at all?3 min▶
A thought exercise, no SQL. Why land data to a file instead of just querying the source every time you need it? Three reasons that matter in production:
- Decoupling: once data is in your file, your pipeline no longer depends on the source being up, fast, or unchanged. The source team can restart their database and your job keeps running on the landed copy.
- Replay: if a downstream transform has a bug, you re-run it against the saved file - you do not have to re-extract from the source, which may have changed or aged out.
- Not hammering prod: repeatedly running heavy analytical queries against a live app database can slow or crash the product. Extract once, then work on your own copy as much as you like.
Try it yourself - this week ◐ 20-30 min total
- List every source system you actually pull from at work and tag each with its shape: relational, file, object storage, API, log, or stream.
- For your friendliest relational source, write a trimmed extract - name only the columns a real report needs, never
SELECT *. - Take Demo 1's script and add a fourth source: land
subscriptionsto a format of your choice and add it to the count summary. - Find one place at work where a job queries a live source repeatedly instead of landing a copy. Note what a single extract-to-file would save.
- Bring one API you struggle to extract from to session b3 - ingestion patterns is where rate limits and incrementals get solved.
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:
Three questions before you go 🎯 ◐ 90 seconds
1 · What is a source system?
A source system is where data is born, upstream of your pipeline - and you usually do not own or control it. Recognising its shape decides how you read it.
2 · Why extract data to a file instead of querying the source every time?
A landed copy frees your pipeline from the source's uptime, lets you replay transforms without re-extracting, and keeps heavy queries off a live app database.
3 · How does a CSV source differ from a JSON source?
CSV is flat, tabular text. JSON carries nested objects and arrays - the shape APIs return - which DuckDB flattens into rows when you read it with read_json_auto.