Ingest, store, transform, serve, and observe: the end-to-end mental model and where bounded vs unbounded data fits.
⏱ 13 min readTopics chapter readerLevel · Foundations
01 · Overview
Start here
Data goes in, rests, changes shape, gets used, and is watched all the way.
⏱ 5 min · Topic 1 of 11
Chapter 1 showed the shop pipeline in five stages: source, ingest, transform, store and serve. Checks and alerts watched all of them.
This chapter counts the stages in a second way, as the life of the data. The five stages are ingest, store, transform, serve and observe.
We follow one night of the same pipeline, the run at 02:00 on 2026-01-22. The data is the same six orders from the SQL chapters.
In this chapter you will learn:
How the five stages of the lifecycle fit together.
Why ingest copies the data and changes nothing.
Why you keep the raw data, and then clean it in layers.
Why the order of the transform steps changes the answer.
How a change in a table can break the dashboards that read it.
What to check at every stage, and why late data exists.
02 · The idea
The five-stage lifecycle
Five stages, run again every night. Ingest and transform leave saved tables.
⏱ 7 min · Topic 2 of 11
The lifecycle is the path data takes through a pipeline. The pipeline repeats it every night.
Chapter 1 called the app database the source. It is where the data starts, so it is not a stage here. Two things change in this view.
Store comes before transform. You save the raw data first, then you change it.
Observe is a stage, with its own checks. It watches the other four.
A warehouse is a database built for analysis. A layer is one table, or a set of tables, at one level of cleaning.
This table follows the 02:00 run on 2026-01-22. Look at the last column. It gives what each stage leaves behind.
Stage
Its job
In the shop
What it leaves
1. Ingest
Copy the data in.
Copy orders 101 to 106 to the warehouse.
6 rows in raw_orders
2. Store
Keep every layer of the data.
Keep the raw copy now, and the tables transform builds later.
Keeps raw_orders, silver_orders and daily_revenue.
3. Transform
Clean the data and add it up.
Remove copies, then add up the paid orders by day.
6 rows, then 4 rows
4. Serve
Give the result to its readers.
The dashboard reads daily_revenue.
None. It only reads.
5. Observe
Check every other stage.
Count rows, check the dates and the values.
A pass or a fail
Transform reads the raw table, and serve reads the table transform builds. So you can fix and re-run transform without copying the data again.
Interview question
"The dashboard still shows the numbers from two days ago. What do you check?" Walk the stages in order. Check ingest, then transform, then the table the dashboard reads.
The next sections open each stage.
03 · Stage 1
Ingest: copy, change nothing
Ingest is a copy machine, not an editor.
⏱ 8 min · Topic 3 of 11
Ingest copies data from the source into the warehouse. The main rule is to copy it exactly as it is and change nothing.
The job adds one column, loaded_at. It records when each row arrived.
This is the job’s first run, so raw_orders is empty before it. Here is the 02:00 run:
Raw now holds order 106 two times. This is fine for now. Ingest only records what arrived, and the next stages fix it.
Watch out
Do not drop rows during ingest. If the job skipped order 105 for its empty amount, the warehouse would have no record of it while pending. If the shop later changes or deletes it, the old version is gone.
04 · Stage 2
Land raw, refine in layers
Keep what arrived. Clean it once. Add it up in a separate step.
⏱ 8 min · Topic 4 of 11
Store means you save the data in layers. The raw layer is the copy exactly as it arrived. Later layers are cleaner.
Our shop uses three layers. This table shows each one with its row count at the end of the run:
Layer
Table
What it holds
Rows
Raw
raw_orders
Every copy, exactly as it arrived.
7 (made up: order 106 twice)
Cleaned
silver_orders
One row for each order.
6
Business-ready
daily_revenue
Paid revenue for each day.
4
Many teams use these names: raw is bronze, cleaned is silver, and business-ready is gold. The names are a habit, not a rule. Some teams use two layers and some use five.
Why keep the raw layer? Only raw shows when each copy arrived.
Silver keeps one row for each order. Raw still shows both copies of order 106:
Ask the raw layer a question only it can answerworked example
SQL
Input data
raw_orders (made up: order 106 was copied twice)7 rows
The two copies arrived at 02:00 and 02:05. A cleaned layer that kept one row could not show this.
If a cleaning rule is wrong, you fix the rule and rebuild the cleaned layer from raw. You do not need to read the shop database again. The shop may have changed those rows by then.
Remember
Raw does not catch a bad rule. It only makes the fix cheap. Checks are a separate job, and observe does it.
05 · Stage 3
From raw to trustworthy
Clean first, then add up. The order of the steps is part of the answer.
⏱ 9 min · Topic 5 of 11
Transform changes raw data into data you can trust. It has two steps in our shop: clean, then add up.
Step one is the clean step. It builds silver_orders with one row for each order. The two copies of order 106 differ only in loaded_at, so we leave that column out:
Clean: one row for each orderworked example
SQL
Input data
raw_orders (made up: order 106 was copied twice)7 rows
Many warehouses support CREATE OR REPLACE TABLE. It rebuilds the table, so a re-run gives the same result.
DISTINCT keeps one copy of rows that are exactly the same. The seven raw rows become six, so order 106 appears once.
If two copies differ in any column, such as a new status, DISTINCT keeps both. Chapter 8, Transformation Fundamentals, shows how to keep one row for each order.
Step two adds up the paid orders for each day. It reads the cleaned table:
The day 2026-01-21 shows 160.00 (80.00 twice) instead of 80.00.
Chapter 1 added up straight from raw. That was safe there, because raw held each order once.
Watch out
Nothing fails here, and the dashboard shows 160.00 for a day that sold 80.00. Clean first, then add up. Chapter 8, Transformation Fundamentals, covers joins and more steps.
06 · Stage 4
Serve: people read the table
A table that people read is a promise about its columns and its meaning.
⏱ 6 min · Topic 6 of 11
Serve gives the finished data to the people and programs that use it. You ran this query in chapter 1, with the table name shortened:
Serve: the dashboard reads the tableworked example
Now the dashboard depends on daily_revenue. Its column names and its meaning are a promise. Here are three changes the data team could make later:
Change to daily_revenue
What the dashboard shows
The team renames revenue to paid_revenue.
An error: the column revenue is not found. Someone fixes it today.
The team adds refunded orders to the filter. The name stays revenue.
No error. The total becomes 180.50, because 2026-01-11 adds 60.00.
The nightly job fails, so the table is not updated.
No error. The dashboard shows old numbers.
The error is the good case, because a person sees it. The other two are silent. Nobody is told, and people decide with a wrong number.
Old numbers are called stale data. Chapter 22, Schema Evolution & Data Contracts, covers how to change a table without breaking its readers.
07 · Stage 5
The stage everyone forgets
Observe turns a silent failure into a loud alert.
⏱ 9 min · Topic 7 of 11
Observe means you check the other four stages and alert a person when a check fails. Every stage above can fail with no error message. Observe finds those failures.
Remember: a schema is the list of columns in a table and their types.
Observe watches four things. This table gives each one with a check for our shop:
Watch
The question
A check for the shop
Freshness
Did the data arrive on time?
The newest loaded_at is less than 24 hours old.
Volume
Did the right number of rows arrive?
The warehouse has as many orders as the source had at 02:00.
Quality
Are the values right?
No paid order has an empty amount.
Schema
Did the columns change?
shop.orders still has the five expected columns.
Here are the volume checks for tonight. We count distinct order ids in raw, because raw holds a retried copy. Each (SELECT …) runs on its own and gives one number:
Observe: count rows at each stageworked example
SQL
Input data
shop.orders (the app database)6 rows
order_id
customer_id
status
amount
order_date
101
1
paid
40.00
2026-01-03
102
1
paid
25.00
2026-01-10
103
2
refunded
60.00
2026-01-11
104
3
paid
15.50
2026-01-15
105
3
pending
NULL
2026-01-20
106
4
paid
80.00
2026-01-21
raw_orders (made up: order 106 was copied twice)7 rows
The answer 0 means the check passes. Order 105 has no amount, but its status is pending, so it is not counted. A paid order with no amount would give 1, and SUM would skip it silently.
Lineage is a map of which table reads which. Ours is shop.orders, then raw_orders, then silver_orders, then daily_revenue, then the dashboard.
If raw_orders is wrong, silver_orders, daily_revenue and the dashboard are all in doubt. shop.orders is not.
Watch out
A check that stops running raises no alarm. Also alert when a check did not run, and never turn off an alert just to stop it. Chapters 20 and 21, Data Quality & Validation and Data Observability & Monitoring, cover checks in full.
08 · Shape
Bounded or unbounded data
You can sum a finished file. You can only sum a river between two moments.
⏱ 8 min · Topic 8 of 11
Bounded data has an end. Yesterday’s orders file is bounded, because all of it exists once the day is over.
Unbounded data never ends. The shop’s order events are unbounded, because a new order can arrive at any second. An event log is a list of events that only grows.
Data that never ends can only be added up between two times. So you choose a cut-off, a moment when the job stops waiting. Anything that arrives after it is late data.
Here is a made-up example. The job at 02:00 adds up orders from 2026-01-21 that arrived before the cut-off:
A cut-off at 02:00worked example
SQL
Input data
order_events (made up: order 107 does not exist in the shop data)2 rows