learn-data-reliability-with-phoebe / Builder session 4 of 8
Learn Data Reliability with Phoebe · Builder session 4 of 8

Runbooks a tired person can follow

Session 3 ended at the page and its first sentence. Tonight is what the page points at. A runbook is not documentation and it is not a debugging guide; it is the shortest path from "my phone just buzzed" to "the settlement report is safe and the right person knows", written for somebody who is tired, has never seen this pipeline, and is reading on a phone. Harborlight's night 9 is the case throughout: fourteen partitions missing, nothing on this side able to fix it, and a runbook whose fourth step was a message rather than a tree of things to try.

🟡 Core Data and analytics engineers Whoever is on call this week 45 min
0-3 · Welcome 3-20 · What a runbook is for at 03:00 20-40 · Build-along: write one for the outage 40-45 · Q&A
Part 0

Where tonight sits in the builder track

Eight sessions, one artifact: a reliability contract for one pipeline. Session 1 measured it, session 2 set the target, session 3 turned the target into a budget and a burn-rate alert that pages five times a month for Harborlight's five incidents. Tonight each of those five pages gets a runbook, bound to it by name, so that the person holding the phone at 03:00 is never asked to remember anything. Session 5 is the backfill the runbook hands off to once the source is back.

Live = presented in the 45 minutes Self-study = the full depth, after class 🔍 Every diagram zooms

★ What you walk out with tonight. One complete runbook for the upstream-outage page, as a text file you can put in a repository: the alert it binds to, three steps that need no judgement, a confirming query with its expected output printed underneath, a pre-agreed safe state for the settlement report, a message template with three blanks, one escalation name and a last-drilled date. A binding from the session 3 alert to that runbook. And a timed drill against night 9's real history, which is the only way to know the page works.

LiveWhy the reader, not the author, decides what a runbook is3 min▶

The person who writes a runbook knows the pipeline, is awake, and is sitting at a keyboard. The person who reads it is none of those things. Night pages at Harborlight went to a rota of six engineers, four of whom had never touched the orders pipeline, and the median page arrived at 02:40. Every design decision on the page follows from writing for that reader: short, exact, and free of anything that asks them to weigh options.

A runbook that says "check whether the source looks healthy" has asked a tired stranger to make a judgement about a system they do not know. A runbook that says "run Q1; the expected output is one row with answered = 0" has asked them to compare two things. The second one works at 02:40. The first one produces a different outcome every night, depending on who is on the rota.

Real world

Harborlight's old runbook for the orders pipeline was a wiki page titled "Orders pipeline troubleshooting", 1,900 words, last edited fourteen months earlier, opening with an architecture diagram. On night 9 the on-call read it for twenty minutes, tried three of its suggestions against a source that was simply down, and told the settlement team at 05:10. The report was late by two hours. The information the settlement team needed had been available at 00:20, when the page fired.

Self-studyThe vocabulary tonight adds3 min▶
  • Runbook - one page, bound to one alert, that takes the reader from the page to a safe state. Not a troubleshooting guide, not an architecture document.
  • Safe state - the condition of the downstream report that the runbook aims for: held, served stale, or scheduled for backfill. Decided per report, in advance, by the people who read that report.
  • The first three steps - confirm the page is real by looking at one thing, decide whether the report is at risk by one rule, tell one named person. The same on every runbook, before any diagnosis.
  • The line - the point on the page below which exactness is required and above which no decision is permitted. Everything above the line is a lookup or a comparison.
  • Confirming query - the one query that proves the page is the incident this runbook covers, with its expected output printed under it so the reader compares rather than interprets.
  • Binding - the alert definition names its runbook; the page carries the link. A page without a link is a page asking the reader to remember where the document lives.
  • Drill - running the runbook against a past night's history with the clock pinned, timed, and recording the date and minutes at the top of the page.
Part 1 · covers structure and the first three steps

The reader is tired, new, and holding a phone

8 min live

A runbook has exactly one job: get a person who did not build the pipeline from a page to a safe state without a judgement call. Everything that serves that job goes on the page. Everything else, however true and however useful in daylight, goes somewhere else.

RUNBOOK · orders / upstream outage binds to: orders.burn.source-down · last drilled: 2026-08-14 · 11 min 1 · confirm the page is real: run Q1, match the expected output 2 · is the settlement report at risk: run Q2, apply the one rule 3 · tell one named person: the duty lead on the rota, then continue no decisions above this line Q1 · the confirming query, with its expected output printed under it safe state · settlement report: HOLD with a banner, pre-agreed message template · three blanks, sent to the settlement channel escalation · 05:30 and still silent: call the head of data platform last drilled 2026-08-14 · walked against night 9 in 11 minutes The reader tired, new to the pipeline, holding a phone. Every line on the page is written for them. Steps 1 to 3 never change Look at one thing. Apply one rule. Tell one person. Identical on all five orders runbooks. Above the line: no judgement calls. Below the line, exactness The exact query and its expected output. The one command. The exact words to send. Who to call, by name, and at what time. Not on the page History, architecture, opinions, and anything that needs a decision. Those go in the incident package, read the next morning. Night 9: step 4 was a message, not a debugging tree. Nothing on this side could fix fourteen missing partitions.
🔍 Click to zoom - the runbook as a page skeleton, with the line above which nothing may need a decision.
LiveThe one job: from a page to a safe state, with no judgement calls2 min▶

Diagnosis is not the job. Fixing is not the job. The job is that, some minutes after the page, the downstream report is in a state its readers agreed to in advance and the person who owns that report knows. If the reader of the runbook also fixes the pipeline, good; the runbook did not ask them to, and it would still have done its job if they had not.

That framing is what makes a runbook usable by somebody who did not build the pipeline. A stranger cannot diagnose an unfamiliar system at 03:00, and a page that asks them to is a page that gets closed and an incident that gets found by the settlement team in the morning. A stranger can run a query, compare its output to a printed expectation, run one command and send one message. So that is what the page asks.

LiveThe first three steps, before any diagnosis3 min▶

Every runbook for the orders pipeline opens with the same three steps, in the same order, and none of the three requires a decision. They are the same on all five runbooks so that the on-call learns them once and does them from memory on any page.

  • 1 · Confirm the page is real by looking at one thing. Run the confirming query. Compare the output to the expected output printed beneath it. If it matches, this is the incident this runbook covers. If it does not, the page names which runbook to open instead. No interpretation of dashboards, no "does this look normal".
  • 2 · Decide whether the settlement report is at risk, by one rule. The report is signed at 07:00 against the previous day's 24 partitions. If the affected partitions belong to a day whose report has not been signed yet, the report is at risk. One query against the sign-off table says which; the reader reads the answer, they do not weigh it.
  • 3 · Tell one named person. The settlement duty lead on the rota, by name, before anything else is attempted. The message is one sentence: what is missing, from when, and that the report is going on hold. Diagnosing first is the mistake every tired engineer makes, because diagnosis feels like progress. Telling the reader of the report is progress.

Only below those three does the page say anything specific to the incident. The rule is drawn on the page as a literal line, and the discipline is that nothing above it may ask the reader to choose.

LiveWhat goes on the page, and what stays off it3 min▶
On the pageOff the page, and where it lives instead
The exact query to run, as text the reader can pasteThe history of the pipeline and its incidents: the incident package, session 6
The exact expected output, printed under the queryThe architecture: the design document, read in daylight
The one command that puts the downstream report in its safe stateOpinions about what should be changed: the postmortem's action list
Who to call, by name, with the number on the rota page it links toAnything that needs a decision: the escalation, who is awake and paid to decide
The message template, with its blanks markedAlternative things to try: a debugging tree is a judgement call in disguise
The date it was last drilled and how long the drill tookExplanations of why the steps work: a footnote at most, after the steps

The left column is short because a phone screen is short. The right column is not deleted; it is moved to the document whose reader is awake. A runbook that carries the right column is a runbook the on-call scrolls past looking for the query, and every scroll at 03:00 is a chance to give up.

LiveNight 9: when the runbook's job was communication3 min▶

At 00:00 on night 9 the orders source API stopped answering. Fourteen partitions in a row did not land. The burn-rate page fired at hour 0. There was nothing on the Harborlight side that could make a source API answer, and the pipeline itself was healthy: it was polling a dead endpoint and correctly writing nothing.

A debugging tree for that night has no useful branches. Restarting the connector produces the same nothing. Checking credentials produces the same nothing. The runbook's step 4 for the upstream outage is therefore not a fix; it is a template message to the settlement team saying what is missing, since when, that the report is held with a banner, that nothing on this side can shorten the wait, and when the next update comes. The most valuable thing the on-call can do for fourteen hours is send that message at 00:20 and rerun the confirming query every thirty minutes until the source answers.

Real world

The settlement team's own account of night 9 was not "the data was late". It was "nobody told us until five". They had staff waiting on a report that could not exist yet, and a finance director asking why. A message at 00:20 would have sent those people home. That is why the outage runbook's most important line is a paragraph of plain prose with three blanks in it.

Part 2 · covers binding, staleness and safe state

One runbook per page, kept true by being used

9 min live

A runbook is only as good as the moment it is opened. Three things decide that moment: whether the page carries a link to exactly one runbook, whether that runbook still describes the pipeline it was written for, and whether the safe state it aims at was agreed by the people who read the report. Under the burn-rate policy from session 3 there are five pages a month, one per incident, so five runbooks cover the month and each is opened about once.

The session 3 policy pages five times a month. The newest bad row chooses which of five runbooks opens. orders.burn.late-landing landed after the deadline, reconciled, all shards RUNBOOK Late load night 4 · 3 bad 1 opened once orders.burn.source-down not landed, and the source probe is silent RUNBOOK Upstream outage night 9 · 14 bad 1 opened once orders.burn.unreconciled landed on time, reconciled = false RUNBOOK Corrupt batch night 15 · 6 bad 1 opened once orders.burn.connector not landed, and the source probe answers RUNBOOK Flapping connector night 21 · 8 bad 1 opened once orders.burn.short-shards landed, shard_count below 3 RUNBOOK Partial load night 27 · 4 bad 1 opened once Openings this month: 1 + 1 + 1 + 1 + 1 = 5, which is the burn-rate policy's 5 pages, 1.2 a week. Under threshold paging the same five runbooks would have been opened 35 times, fourteen of them on night 9 alone. A runbook opened once a month is never kept true by use. The quarterly drill is what keeps it true.
🔍 Click to zoom - five alerts bound to five runbooks, one per incident type, each opened about once a month.
LiveOne alert, one runbook, bound by name3 min▶

The burn-rate policy from session 3 is one query, and it decides whether to page. It does not decide which runbook. That comes from the newest bad row in the partition log and one probe of the source: did the partition land at all, was it late, was it reconciled, did all three shards arrive, and is the source answering. Those fields sort every incident Harborlight has had into one of five shapes, and each shape is an alert with a name.

  • The alert definition names the runbook. orders.burn.source-down names runbooks/orders/upstream-outage.md and nothing else. One alert, one runbook. An alert that could mean two runbooks is a decision left for the reader, above the line.
  • The page carries the link. The on-call taps the page and the runbook opens. Nobody searches a wiki at 03:00; a runbook that has to be found is a runbook that is not opened.
  • The window that fired is not the qualifier. Which window crossed its line says how fast the budget is burning, not what broke. Night 9 and night 21 both paged on the fast window at their first bad hour; the log row is what tells them apart, so the log row picks the name.
★ The binding, as the alert definition carries it # alerts/orders.yaml - alert: orders.burn.source-down policy: orders-burn-rate # session 3: 2 h at 14.4x or 12 h at 3x, rising edge, 12 h suppression qualifier: newest bad partition has landed_at null and the source probe has not answered runbook: runbooks/orders/upstream-outage.md # exactly one owner: data-platform-oncall last_drilled: 2026-08-14 drill_minutes: 11
LiveStaleness: a runbook nobody has run describes a different pipeline3 min▶

Pipelines change every week. Runbooks change when somebody remembers. Six months after it was written, a runbook's confirming query references a column that was renamed, its hold command points at a report that moved, and its named person left in March. Nobody notices, because with five pages a month across five runbooks each one is opened roughly once a month, and the person opening it at 03:00 is the least equipped person in the company to spot that the page is out of date.

The fix is to run it when nothing is wrong. A drill takes a past night from the partition log, pins the clock to an hour inside the incident, and walks the runbook top to bottom exactly as the on-call would, timing it. The confirming query either returns the printed expected output or it does not. The hold command either works against a staging copy of the report or it does not. The named person either answers or has left. Quarterly is the cadence that catches a rename before a real page does, and the date and minutes go at the top of the page, so the next reader knows how fresh the page is before they trust it.

A drill that takes longer than the last one is a finding. Night 9's runbook walked in 11 minutes in August. If it walks in 19 minutes in November, either the runbook grew or the pipeline moved, and either way the page is no longer the shortest path.

LiveSafe state is a section, and a pre-agreed decision per report3 min▶

"Safe" means something different for every downstream reader, and the on-call is the wrong person to decide which. A settlement report that is signed and paid against must never show a stale total, so its safe state is hold with a banner. An operations dashboard that people glance at is better stale than blank, so its safe state is serve stale with a banner saying how old the numbers are. A demand forecast retrained nightly can wait a day, so its safe state is backfill later, with no banner and a ticket.

Downstream reportPre-agreed safe stateAgreed withThe one command
Settlement reportHold, banner on the report page, nothing servedFinance, the duty lead who signs itharbor report hold settlement --banner "..."
Operations dashboardServe stale, banner with the age of the dataOperations leadharbor dash stale ops --since {first_missing}
Demand forecastBackfill later, no banner, ticket to planningPlanningharbor ticket open planning --template backfill

Those three rows were decided in a meeting with the people who read each report, in daylight, once. The runbook's safe-state section is one row of that table copied in, with its command. The on-call runs the command. They do not revisit the meeting.

Self-studyFive pages, five runbooks, each opened about once2 min▶
  • The month's shape sets the runbook's shape. Under burn-rate alerting Harborlight's month is 5 pages, one per incident, 1.2 a week. Five runbooks cover it completely, and each is opened about once. That is why they are short: a document read once a month is read by somebody who has forgotten it.
  • Threshold paging would have opened them 35 times. Fourteen openings of the outage runbook on night 9 alone, each one after a page saying the same thing. The runbook's design assumes the session 3 policy; under threshold paging the reader would be on step 1 for the fourteenth time at 13:00.
  • Once a month is not enough to keep a page true. Hence the drill. A runbook that is used daily corrects itself; a runbook used monthly has to be exercised on purpose.
  • Every bad partition weighs the same here, and so does every runbook. The corrupt-batch runbook protects a signed number and the late-load runbook protects an hour of waiting. A more mature set would order the five by the harm they prevent. Start with five of equal weight and let the incident packages from session 6 tell you which to invest in.
Build-along

Write the runbook for the upstream outage

★ 20 min · everyone builds

The upstream outage is the runbook to write first, because it is the one where the temptation to debug is strongest and the right action is a message. Use Harborlight's partition log and night 9's history; if you have your own pipeline and an outage in its past, substitute that night and the shape does not change. The runbook is a text file. Put it where the alert definition can link to it.

Write the page, top to bottom, as text. Title, the alert it binds to, the three unchanging steps, the confirming query with its expected output printed beneath, the safe-state decision for the settlement report with its one command, the message template, the escalation name and time, and the last-drilled line. Draw the line as a literal row of dashes.

Write the confirming query against the partition log. Partitions with no landed_at in the last six hours, joined to the source probe and grouped by source, so one row tells the reader both that partitions are missing and that the source is silent. Run it with the clock pinned to night 9 at 03:00 and paste the real output under it as the expected output.

Write the second query for the settlement report's risk. One lookup against the sign-off table for the day the missing partitions belong to. The rule is printed with it: no row or no signature means the report is at risk.

Bind the runbook to the alert by name. In the alert definition, orders.burn.source-down names this file and no other. Check that the page the alert emits carries the link; open it from a phone.

Drill it against night 9 and time the walk-through. Pin the clock to night 9 at 03:00, hand the page to somebody who did not write it, and start a timer. They run Q1 and compare, run Q2 and read the rule, name the person, run the hold command against the staging report, fill the template's three blanks. Stop the timer when the message is ready to send. Harborlight's drill: 11 minutes.

Record the drill date and minutes at the top of the page and in the alert definition. Both, so the page and the binding agree. Put the next drill in the calendar for one quarter out.

★ Step 1 · the runbook, as the file the alert links to RUNBOOK · orders / upstream outage v4 · last drilled 2026-08-14 · 11 min binds to alert orders.burn.source-down (one alert, one runbook; the page carries this link) reader whoever is on call, on a phone, who may never have seen this pipeline ==================================================================================== NO DECISIONS ABOVE THIS LINE · steps 1 to 3 are the same on every orders runbook 1 CONFIRM THE PAGE IS REAL Run Q1 below. Expected: one row, source orders-api, last_answered at or before first_missing, missing_partitions 1 or more. Anything else: this is not an outage. Open runbooks/orders/late-load.md instead. 2 IS THE SETTLEMENT REPORT AT RISK Run Q2 below. Expected: no row, or signed_at empty = yes, at risk. Continue to 3 and 4. signed_at present = already signed. Still do 3, then stop; the backfill runbook owns the rest. 3 TELL ONE NAMED PERSON Dana Whitlock, settlement duty lead (rota week 33). Number on the rota page linked here. Say: "orders source outage, partitions missing from {first_missing}, report going on hold." Do this before anything below. Do not diagnose first. ------------------------------------------------------------------------------------ 4 SAFE STATE · pre-agreed for the settlement report: HOLD. Never serve it stale. harbor report hold settlement --banner "orders source outage - partitions from {first_missing} missing" Expected: "settlement: held, banner set". Open the report URL; the banner is showing. 5 SEND THE TEMPLATE MESSAGE To #settlement-ops and the duty lead. Template below, three blanks. Fill, send, note the time. 6 RECHECK EVERY 30 MINUTES Rerun Q1. When last_answered is newer than the last missing hour, the source is back: post "source answering since {time}" and open runbooks/orders/backfill.md. Do not backfill from this page. ESCALATE 05:30 and the source is still silent: call Idris Amara, head of data platform. The report deadline is 07:00 and the hold decision becomes theirs. NOT ON THIS PAGE why the source fails, the connector's history, what to change. Those belong to the incident package, written tomorrow.
★ Step 2 · Q1, the confirming query, with the expected output printed under it -- Q1 · missing partitions in the last 6 hours, and whether the source answered with missing as ( select partition_hour from ops.orders_partition_log where partition_hour >= dateadd('hour', -6, current_timestamp) and landed_at is null ), probes as ( select source, count(*) as probes, count_if(reachable) as answered, max(case when reachable then probed_at end) as last_answered from ops.source_probe where probed_at >= dateadd('hour', -6, current_timestamp) group by source ) select p.source, p.probes, p.answered, p.last_answered, (select count(*) from missing) as missing_partitions, (select min(partition_hour) from missing) as first_missing from probes p; -- expected on this page: one row, source orders-api, last_answered at or before first_missing, missing_partitions >= 1 -- night 9 at 03:00: orders-api · probes 6 · answered 2 · last_answered night 8 23:00 · missing_partitions 3 · first_missing night 9 00:00
★ Step 3 · Q2, is the settlement report at risk -- Q2 · has the report for the day of the missing partitions been signed select report_day, signed_at, signed_by from fin.settlement_signoffs where report_day = date(:first_missing); -- rule: no row, or signed_at empty = at risk. A signed_at = already signed; the backfill runbook owns what happens next. -- night 9 at 03:00: no row. At risk. Go to step 3, then step 4.
★ Step 1, continued · the message template Subject: orders source outage · settlement report on hold Since {first_missing} the orders partitions have not landed. The orders API has not answered a probe since {last_answered}. The settlement report is held with a banner. No stale total will be served. Nothing on the Harborlight side can shorten this. Next update at {now plus 60 minutes}, or sooner if the source answers. On call: {your name}. Runbook: orders / upstream outage.
★ Step 6 · the drill record insert into ops.runbook_drills (runbook, drilled_on, against_night, pinned_clock, walked_by, minutes, findings) values ('orders/upstream-outage', date '2026-08-14', 9, 'night 9 03:00', 'rota engineer, not the author', 11, 'Q1 expected output pasted from real history; hold command tested on staging; rota name refreshed'); -- and the same date and minutes at the top of the page and in alerts/orders.yaml, so the three agree

Pin the clock, do not edit history. The drill replaces current_timestamp in Q1 and Q2 with night 9 at 03:00 and runs against the real partition log. Nothing in the log changes, and the expected output printed on the page is real output, not a guess about what the query would return.

Homework

Before session 5

30 min
  1. Write the other four runbooks for your pipeline, one per alert name, each opening with the same three steps word for word. If two of them turn out identical below the line, you have four incident types, not five, and the alert definitions should say so.
  2. Hold the safe-state meeting. Sit with the person who reads each downstream report and fill in the three-column table: hold, serve stale, or backfill later, with the one command. Copy each row into its runbook.
  3. Drill one runbook you did not write, against a real past night, with a timer. Record the date and minutes at the top of the page. Note every line where you hesitated; each one is a decision that has crept above the line.
  4. Find the most recent runbook in your team that nobody has opened in six months and run its confirming query. Write down what happened.
Check yourself

Three questions

A page fires at 02:40. The on-call opens the runbook and its first step reads "check whether the source looks healthy and, if so, restart the connector". What is wrong with that step?

"Looks healthy" is a decision handed to a tired stranger. Above the line every step is a lookup or a comparison, and the person who reads the report is told before anything is tried.

On night 9 the source API was down for fourteen hours and nothing on Harborlight's side could bring it back. What should the outage runbook's step 4 be?

Every branch of a debugging tree produces the same nothing against a dead source. The settlement team's complaint about night 9 was not that the data was late; it was that nobody told them until five.

Under the burn-rate policy Harborlight's five runbooks are each opened about once a month. Why does that make a quarterly drill necessary rather than optional?

A runbook used daily keeps itself true. Five pages a month over five runbooks is one opening each, by the least equipped reader, so staleness has to be caught in daylight.

Sources covered

What tonight maps to

✓ Runbooks as professional norm - structure, the first three steps, what not to include Established practice, taught to working depth: the one job, the line above which nothing needs a decision, the in-and-out table, and the safe state as a pre-agreed section per report.
✓ Binding to the multi-window burn-rate policy from session 3 One alert, one runbook, by name. The bench's 5 pages a month, one per incident, is what makes five runbooks cover the month and each be opened about once.
✓ Drills against real history The clock pinned inside a past incident, the walk-through timed, the date and minutes recorded at the top of the page and in the alert definition.
◐ The backfill the runbook hands off to Session 5. Tonight's runbook ends at "the source is answering again" and opens the backfill runbook by name.
◐ The incident package, and why the history stays off the runbook Session 6. History, architecture and opinions live in the document whose reader is awake.
◐ How a bad partition gets noticed The data observability course owns detection. Here the page has already fired and only has to be confirmed.
· Traces, spans and alerting for AI services Not covered by design. That is the AI observability course; this track is tables, partitions and the people on call for them.

Session 4 cheat sheet

One jobFrom a page to a safe state, by a stranger, with no judgement call. Fixing is optional.
Three steps, always the sameConfirm with one query. Decide the report's risk by one rule. Tell one named person.
The lineAbove it, lookups and comparisons only. Below it, exactness: the query, its output, the command, the words.
Off the pageHistory, architecture, opinions, debugging trees. They live where the reader is awake.
Night 9's step 4 is a messageNothing on this side could fix a dead source. The template with three blanks is the fix.
One alert, one runbook, by nameThe log row picks the alert; the alert names the file; the page carries the link.
Safe state is pre-agreedHold, serve stale, or backfill later. Per report, decided in daylight with its reader.
Drill quarterly5 pages a month, 5 runbooks, each opened once. Pin the clock, time the walk, record the date.