Backfill a week of reports
A nightly job counts the bakery’s orders, one run per day. Backfill the week, fail a load, rerun a day, and check every total against the source.
| Day | Wait for the file | Extract | Convert | Load | Check | Report | Source |
|---|---|---|---|---|---|---|---|
| Monday 19 Octoberdone | succeeded | succeeded | succeeded | succeeded | succeeded: the totals match | 195 | 195 |
| Tuesday 20 Octoberdone | succeeded | succeeded | succeeded | succeeded | succeeded: the totals match | 379 | 379 |
| Wednesday 21 Octoberfailed | succeeded | succeeded | succeeded | succeeded | failed: the totals differ | 275+96 | 179 |
| Thursday 22 Octoberdone | succeeded | succeeded | succeeded | succeeded | succeeded: the totals match | 370 | 370 |
| Friday 23 Octoberdone | succeeded | succeeded | succeeded | succeeded | succeeded: the totals match | 343 | 343 |
| Saturday 24 Octoberdone | succeeded | succeeded | succeeded | succeeded | succeeded: the totals match | 333 | 333 |
| Sunday 25 Octoberdone | succeeded | succeeded | succeeded | succeeded | succeeded: the totals match | 213 | 213 |
| after the week | Filed here by the clock | ||||||
| Week | 2,108+96 | 2,012 | |||||
- Not started
- Running
- Succeeded
- Failed
- Waiting to retry, at the time under it
- Not run, as a step before it failed
Every run has finished, and Wednesday's failed. Wednesday's report counts 275 orders, 96 more than the source: a failed load left 96 rows behind, and the retry appended all 179 again; the other 6 days match.
Over time, each day's steps: a hollow bar is a sensor waiting for its file, a filled one a step at work, and a cross an attempt that failed. A dotted track with nothing on it is a run waiting for a slot or a retry, and the upright line is now.
Checked after every event
- Every day's total matches the sourceBroke once, first at 00:41: Wednesday's report holds 275 orders and the source 179.
- No step started before the steps it needsHeld.
- No more runs or loads at once than allowedHeld.
02:10 on Monday 26 October, 44 events, seed 9
What happened
- 00:00 Wednesday's next load attempt will fail part way through
- 00:00 The job graph is switched on with catch-up, which creates all 7 days' runs at once; 3 may run at a time
- 00:00 Monday's run starts
- 00:00 Tuesday's run starts
- 00:00 Wednesday's run starts
- 00:00 Monday's export file is already there, so its sensor succeeds at its first look
- 00:00 Tuesday's export file is already there, so its sensor succeeds at its first look
- 00:00 Wednesday's export file is already there, so its sensor succeeds at its first look
- 00:19 Tuesday's load appends 379 rows: the report now holds 379 for Tuesday
- 00:19 Wednesday's load gets a warehouse slot after waiting 3 minutes
- 00:20 Tuesday's check finds 379 orders in the report, as in the source
- 00:20 Tuesday's run succeeds
- 00:20 Thursday's run starts
- 00:20 Thursday's export file is already there, so its sensor succeeds at its first look
- 00:21 Monday's load appends 195 rows: the report now holds 195 for Monday
- 00:23 Monday's check finds 195 orders in the report, as in the source
- 00:23 Monday's run succeeds
- 00:23 Friday's run starts
- 00:23 Friday's export file is already there, so its sensor succeeds at its first look
- 00:24 Wednesday's load fails after appending 96 of its 179 rows, and they stay in the report; it is retried in 5 minutes
- 00:39 Wednesday's load appends 179 rows: the report now holds 275 for Wednesday
- 00:39 Friday's load gets a warehouse slot after waiting 3 minutes
- 00:41 Thursday's load appends 370 rows: the report now holds 370 for Thursday
- 00:41 Wednesday's check finds 275 orders in the report and 179 in the source: 96 too many, so it fails and is not retried
- 00:41 Wednesday's run fails: its check found the totals differ
- 00:41 Saturday's run starts
- 00:41 Saturday's export file is already there, so its sensor succeeds at its first look
- 00:43 Thursday's check finds 370 orders in the report, as in the source
- 00:43 Thursday's run succeeds
- 00:43 Sunday's run starts
- 00:51 Friday's load appends 343 rows: the report now holds 343 for Friday
- 00:53 Friday's check finds 343 orders in the report, as in the source
- 00:53 Friday's run succeeds
- 01:04 Saturday's load appends 333 rows: the report now holds 333 for Saturday
- 01:06 Saturday's check finds 333 orders in the report, as in the source
- 01:06 Saturday's run succeeds
- 01:34 Sunday's export file lands
- 01:43 Sunday's sensor finds the export file after 1 hour
- 02:09 Sunday's load appends 213 rows: the report now holds 213 for Sunday
- 02:10 Sunday's check finds 213 orders in the report, as in the source
- 02:10 Sunday's run succeeds
- 02:10 Every run has finished: 6 succeeded and 1 failed
What a backfill promises
The bakery’s report counts each day’s orders. A job runs once per day of data: it waits for that day’s export file, extracts it, converts it, loads it into the report and checks the report’s count against the source. A run belongs to its day, not to the day it happens to run on. That is what makes a backfill possible: switch the job on at the end of the week, and it runs all seven days, three at a time.
It also means every day can run more than once. A load fails half way and retries, someone clears a day to rerun it after a fix, a backfill overlaps a day that already ran. A run that adds its rows each time counts that day twice. A run that replaces its day’s rows in one step can run any number of times and leave the same report. That second kind is idempotent, and it is the only kind a backfill is safe with.
Three things to try
Watch Wednesday count twice. The first run does it: Wednesday’s first load fails after writing 96 of its 179 rows, the retry appends all 179, and Wednesday’s check finds 275 orders against the source’s 179. Switch the load to overwrite the day and start over. Every day matches: 2,012 orders.
Write to today. Make each load write to the day the clock says instead of its run’s own day, and start over. Every day’s orders land on the same date, and every check fails. A job that asks the clock for its date cannot be backfilled.
Rerun a finished day. Clear Tuesday after its run succeeds. With append, Tuesday’s total doubles. With overwrite, nothing changes.
Then make Monday’s file five hours late with one run at a time, and watch the week wait behind it.
What the lines and lamps mean
Each row is a day and each lamp a step of its run, from waiting for the file to the check. A lit lamp is running, a filled lamp has succeeded, a hollow lamp has not started, and a cross has failed; a step waiting to retry carries its next try’s time. The column on the right is the day’s total in the report against the source. The three properties under the board are checked after every event.
Credits
Runs that belong to their day of data, and loads that replace a day’s rows instead of adding to them, are the core of Maxime Beauchemin’s essay “Functional Data Engineering: a modern paradigm for batch data processing” (2018).
What it costs
Nothing to run. Every file is static, and your browser runs the simulation.
How it is built
The job graph is one TypeScript module: five steps for each day, from a sensor that waits for the export file to the check, a scheduler that lets runs in oldest day first, 3 at a time unless you choose otherwise, and 2 warehouse slots that every load shares. An extract or a load fails on its own 8% of the time, and each gets 3 attempts, 5 and then 10 minutes apart. Time, the export files and every failure belong to a simulator around it: virtual time in minutes, one seeded random stream, and your presses recorded where they happened. The same seed and the same presses give the same week. That is how the page opens with a picture the build drew, 44 events after the job was switched on, and how your browser then takes over at the same minute.
After every event, a checker tests the three properties, and that no row appears or vanishes without a write. The same simulator runs 3,000 seeded weeks of failed loads, late files, reruns and changed limits in the site's tests. With overwrite and the run's own day, every property holds. With append, a retry or a rerun counts a day twice, and with the clock's day a week's rows land on one date. Three deliberately broken schedulers prove those runs can find a real bug: one that starts a step before the one it needs has succeeded, one that runs a third load beside the warehouse's 2, and one whose overwrite deletes a day in one step and inserts its rows in another.