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.

The run board: a row for each day, and a lamp for each step of its run
DayWait for the fileExtractConvertLoadCheckReportSource
Monday 19 Octoberdonesucceededsucceededsucceededsucceededsucceeded: the totals match195195
Tuesday 20 Octoberdonesucceededsucceededsucceededsucceededsucceeded: the totals match379379
Wednesday 21 Octoberfailedsucceededsucceededsucceededsucceededfailed: the totals differ275+96179
Thursday 22 Octoberdonesucceededsucceededsucceededsucceededsucceeded: the totals match370370
Friday 23 Octoberdonesucceededsucceededsucceededsucceededsucceeded: the totals match343343
Saturday 24 Octoberdonesucceededsucceededsucceededsucceededsucceeded: the totals match333333
Sunday 25 Octoberdonesucceededsucceededsucceededsucceededsucceeded: the totals match213213
Week2,108+962,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.

00:0001:0002:0003:00

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

Speed
How a load writes

Runs at once
Which day a load writes
Day

What happened

  1. 00:00 Wednesday's next load attempt will fail part way through
  2. 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
  3. 00:00 Monday's run starts
  4. 00:00 Tuesday's run starts
  5. 00:00 Wednesday's run starts
  6. 00:00 Monday's export file is already there, so its sensor succeeds at its first look
  7. 00:00 Tuesday's export file is already there, so its sensor succeeds at its first look
  8. 00:00 Wednesday's export file is already there, so its sensor succeeds at its first look
  9. 00:19 Tuesday's load appends 379 rows: the report now holds 379 for Tuesday
  10. 00:19 Wednesday's load gets a warehouse slot after waiting 3 minutes
  11. 00:20 Tuesday's check finds 379 orders in the report, as in the source
  12. 00:20 Tuesday's run succeeds
  13. 00:20 Thursday's run starts
  14. 00:20 Thursday's export file is already there, so its sensor succeeds at its first look
  15. 00:21 Monday's load appends 195 rows: the report now holds 195 for Monday
  16. 00:23 Monday's check finds 195 orders in the report, as in the source
  17. 00:23 Monday's run succeeds
  18. 00:23 Friday's run starts
  19. 00:23 Friday's export file is already there, so its sensor succeeds at its first look
  20. 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
  21. 00:39 Wednesday's load appends 179 rows: the report now holds 275 for Wednesday
  22. 00:39 Friday's load gets a warehouse slot after waiting 3 minutes
  23. 00:41 Thursday's load appends 370 rows: the report now holds 370 for Thursday
  24. 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
  25. 00:41 Wednesday's run fails: its check found the totals differ
  26. 00:41 Saturday's run starts
  27. 00:41 Saturday's export file is already there, so its sensor succeeds at its first look
  28. 00:43 Thursday's check finds 370 orders in the report, as in the source
  29. 00:43 Thursday's run succeeds
  30. 00:43 Sunday's run starts
  31. 00:51 Friday's load appends 343 rows: the report now holds 343 for Friday
  32. 00:53 Friday's check finds 343 orders in the report, as in the source
  33. 00:53 Friday's run succeeds
  34. 01:04 Saturday's load appends 333 rows: the report now holds 333 for Saturday
  35. 01:06 Saturday's check finds 333 orders in the report, as in the source
  36. 01:06 Saturday's run succeeds
  37. 01:34 Sunday's export file lands
  38. 01:43 Sunday's sensor finds the export file after 1 hour
  39. 02:09 Sunday's load appends 213 rows: the report now holds 213 for Sunday
  40. 02:10 Sunday's check finds 213 orders in the report, as in the source
  41. 02:10 Sunday's run succeeds
  42. 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.

Up and down to move, Enter to open, Esc to close