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

The incident package

An incident is over when the backfill lands. What survives it is a document, and most of those documents are written a week later by the person who was most tired, from memory, in a shape that changes every time. Tonight the package is generated instead: a timeline the partition log and the alert log produce, impact stated as a share of the error budget, and actions that carry an owner and a date. Harborlight's night 9 is the worked case, because 14 bad partitions is two thirds of the month's budget and the room will want to blame the upstream.

🟠 Advanced Data and analytics engineers 45 min
0-3 · Welcome 3-20 · Timeline, impact, actions 20-40 · Build-along: package night 9 40-45 · Q&A
Part 0

Where session 6 sits

Sessions 1 to 5 built the contract: indicators, a target, a budget with burn-rate alerts, a runbook a tired person can follow, and a backfill that does not break the settlement report. Session 6 is what happens after each incident, and it is written for two readers: the on-call who was there and needs the actions, and the leader who was not and needs to decide what to fund. Session 7 adds toil and session 8 replays the whole month on the bench.

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

★ What you walk out with tonight. A query that produces an incident timeline from the logs you already keep, so every package has the same six timestamps in the same order. Impact as a number the budget conversation can use: night 9 was 14 partitions, two thirds of 21.6. A test that separates an action from a wish. And an incidents table that lets five packages be read as one, which is where the finding about nights 21 and 27 comes from.

LiveWhy the package is generated, not remembered3 min▶

A postmortem written from memory has three problems that no amount of good intent fixes. The times are wrong, because nobody at 02:00 was writing them down. The shape is different every time, because each author decides what matters. And it can be argued with, because a recollection is an opinion about what happened. A timeline produced by a query has none of those problems: the timestamps were recorded by machines at the moment, the six fields are the same for every incident, and the only argument available is about the query, which is a better argument to have.

The partition log from session 1 and the alert log from session 3 already hold everything the timeline needs. The package is a view over two tables plus three lines a human writes: conditions, actions, and who reads the stale data.

Real world

Harborlight's first postmortem for night 9 said the outage "lasted about six hours" and that the on-call "responded promptly". The partition log says 14 bad partitions from 01:00 to 14:00 and the alert log says seven minutes from page to acknowledgement. Both facts are kinder to the on-call than the memory was, and neither needed anyone to remember anything.

Self-studyThe six timestamps, and where each one lives3 min▶
  • First bad partition - the earliest partition hour in the incident that fails any of freshness, correctness or coverage. Partition log.
  • Page time - when the alert fired. Alert log. Under burn-rate alerting this is the first bad hour of the incident; under threshold paging it is the same moment, followed by thirteen more on night 9.
  • Acknowledgement - when a human took the page. Alert log. Page to acknowledgement is the number the rota design in leader session 3 is judged on.
  • Safe state reached - when the runbook's safe-state step completed: downstream refresh paused, partitions marked stale, readers told. Alert log, written by the runbook step itself.
  • Last bad partition - the latest partition hour in the incident that failed. Partition log.
  • Backfill complete - when the last bad partition was replaced by a good one. Partition log, the backfilled_at column session 5's idempotent job writes.
Part 1 · covers timeline, impact in budget terms, actions with owners

Three sections, generated not remembered

9 min live

A package has three sections and a fixed order: what happened and when, what it cost, what changes. The first is a query. The second is arithmetic on the budget from session 3. Only the third needs judgement, and the judgement is checked by a test.

Night 9, generated from two logs (not to scale) 01:00 first bad partition partition log 02:04 paged alert log 02:11 acknowledged alert log 02:40 safe state reached alert log 14:00 last bad partition partition log 16:35 backfill complete partition log backfill running 14 bad partitions, 01:00 to 14:00, each an hour the settlement report read stale 1 h 04 to page 7 min to ack 29 min to safe state source down 12 h 2 h 35 to backfill Every timestamp above is a query result. The elapsed times are subtraction. Nothing was typed from memory.
🔍 Click to zoom - night 9's timeline as the two logs produce it, with the 14 bad partitions as the span.
LiveThe timeline: six timestamps, two tables, one query3 min▶

The partition log gives the first bad partition, the last bad partition and the backfill completion. The alert log gives the page, the acknowledgement and the safe state. Join them on the incident window and the timeline is a single row. Because it is a query, night 4's timeline has the same six columns as night 9's, and the leader reading five packages can compare page-to-acknowledgement across all of them without translating five different prose styles.

The elapsed times fall out by subtraction: an hour and four minutes from the first bad partition to the page, because the partition's deadline is the hour plus sixty minutes of grace and the detector ran four minutes after; seven minutes to acknowledgement; twenty-nine minutes to a safe state. Those three intervals are what the rota and the runbook are for, and the timeline is the only place they are measured.

LiveImpact in budget terms, not adjectives3 min▶

"Severe" and "major" are not numbers, and a budget conversation needs a number. The impact section states how many partitions went bad and what share of the error budget that was at the current target. Night 9 was 14 partitions. At a 97 percent target the month's budget is 21.6 partitions, so night 9 alone spent two thirds of it, and with night 4's three already gone the budget was exhausted six nights later on night 15. Stated that way, the outage is not a story; it is the reason the settlement team was promised something the pipeline could not keep.

The second half of impact is who read the stale data. The lineage table lists which downstream jobs read which partitions, so the package can say "the settlement report read all 14 stale partitions; the seller payouts batch read 6 before the backfill landed" instead of "downstream reports were affected".

Real world

The service-reliability workbook's worked example is an incident that consumed 13 percent of a budget, and the point of the example is that 13 percent is a number the business can weigh against a launch. Night 9 at Harborlight consumed two thirds. Once that was on a slide, the dependency SLO with the source team stopped being a nice idea and became a funded action.

Self-studyActions with an owner and a date3 min▶

An action is a change to the system, the runbook or the contract that would have made the incident shorter, smaller or impossible. It carries three fields and is rejected without any one of them:

  • Owner - one named person, not a team. A team owning an action is the same as nobody owning it, because everybody on the team assumes somebody else is on it.
  • Date - a day, not a sprint or a quarter. The set review in a month checks the date against the calendar.
  • Verifiable when done - a check that returns true or false: a row exists in the contracts table, a runbook step exists and was dry-run, a query returns a count. If nobody can tell whether it is done, it is not an action.

Two or three actions per package is normal. Eight is a sign the package is listing everything that could be better rather than what would have changed this night.

Part 2 · covers contributing conditions and the set review

Contributing conditions, not a root cause, and the action that is a wish

8 min live

A single root cause is a comfortable story: find the one thing, fix the one thing, close the ticket. Real incidents in real pipelines are a coincidence of conditions, each of which was true for months without an incident. Night 9 had three, and removing any one of them would have changed the night.

1 · Timeline (generated) 01:00 first bad partition 02:04 paged · 02:11 acknowledged 02:40 safe state reached 14:00 last bad partition 16:35 backfill complete partition log joined to alert log 2 · Impact in budget terms 14 bad partitions of 24 that night two thirds of 21.6 at 97 percent budget exhausted on night 15 stale reads: settlement report 14, seller payouts 6, ops dashboard 14 budget arithmetic plus lineage table 3 · Actions: owner, date, check dependency SLO with the source team Mara · by the 20th · row in contracts safe-state step added to the runbook Tomas · by the 15th · dry-run once rejected: "monitor upstream closely" no owner, no date, nothing to verify Contributing conditions: all three true, none alone the cause the upstream feed went down no dependency SLO with the source runbook stopped at diagnosis Root cause: the upstream outage one cause invites one fix, and the fix belongs to another team Sections 1 and 2 are queries. Section 3 is judgement, tested. The conditions are three lines a human writes.
🔍 Click to zoom - the package as three panels, the three conditions beneath, and the single root cause struck out.
LiveThree conditions, all true, none alone the cause3 min▶

Night 9's upstream feed went down for twelve hours. That is a fact, and it is where a root-cause postmortem stops: the cause was upstream, the fix is theirs, closed. But the same feed had been flaky for a year without a 14-partition incident, so the outage alone does not explain the night. Two more conditions were true. There was no dependency SLO with the source team, so nobody at Harborlight had a number for how late the feed was allowed to be, and nobody at the source knew a settlement report depended on it. And the runbook stopped at diagnosis: it told the on-call how to confirm the feed was down and nothing about what to do next, so the safe state took twenty-nine minutes of improvisation.

Each condition maps to an action Harborlight can own. The outage maps to a fix another team owns and may never make. Contributing conditions are not a softer way of saying root cause; they are the list of things you can actually change.

LiveThe action test, and the action that is a wish3 min▶

Run every proposed action through three questions: who, by when, and how will anyone know. "Add a coverage check on shard count, owner Mara, by the 20th, verified by a row in the indicators table" passes all three. "Be more careful with upstream changes" fails all three, and it is the most common action in postmortems everywhere, because it feels like accountability and costs nothing.

★ An action Add a coverage check on shard count to the orders partition log. Owner: Mara. Due: the 20th. Done when: ops.indicators has a row named orders_coverage_shards and night 27's four partitions fail it on replay.
✗ A wish Be more careful with upstream changes and monitor the feed more closely.

A wish in an actions list is worse than an empty list, because it lets the package close. The set review a month later finds it unverifiable and the same conditions still true.

Self-studyWritten for a reader who was not there3 min▶

The package is the input to the postmortem review on the leader track, and the reader there was asleep on night 9. Everything they need has to be on the page, which changes how it is written:

  • No pronouns without names. "We paused the refresh" becomes "the on-call (Tomas) paused the settlement refresh at 02:40".
  • No system nicknames. The feed has a table name and an owning team; the package uses both.
  • Impact before narrative. The leader reads the budget share first and decides whether to read the rest. Fourteen partitions and two thirds of the budget earns the rest.
  • Blameless by construction. The timeline is a query and the conditions are properties of the system, so there is no sentence in the package that is about a person's judgement. That is what makes people willing to write the honest version.
Self-studyThe set review: what five packages say that one cannot3 min▶

Because the packages land in a table with the same columns, the five incidents of the month can be queried together. Night 21's flapping connector and night 27's partial load look unrelated in their own packages: one is a network problem, the other a source that shipped two shards of three. Read together, both list the same contributing condition: no coverage check on shard count, so partitions landed on time, reconciled, and missing a third of their rows, and nothing noticed for hours. One action, owned once, closes a condition that two separate packages would each have asked for separately or not at all.

The set review is also where actions are checked against their dates. An action past due with no verification is a wish that was well dressed.

Build-along

Package night 9

★ 20 min · everyone builds

Bring the partition log from session 1 and the alert log from session 3. If you are working from Harborlight's data, night 9 is the upstream outage: 14 bad partitions, and the package should come out at two thirds of the budget at 97 percent without anyone typing 14.

Generate the timeline. Select night 9's bad partitions from the partition log, find the alerts inside that window in the alert log, and produce one row with six timestamps. Do not edit the result; if it looks wrong, the log is wrong and that is a finding.

Compute impact. Count the bad partitions and divide by the budget at the current target: 720 times one minus the target. At 97 percent, 14 over 21.6.

List the downstream readers. Join the bad partitions to the lineage table and count, per consumer, how many stale partitions were read before the backfill landed.

Write the contributing conditions as three lines. Each is a property of the system that was true before the night began. If a line names a person, rewrite it until it names a system.

Write two actions and reject one wish. Owner, date, verification for each. Read your list aloud and strike anything that does not answer all three.

Write the package to the incidents table. One row for the incident, one row per condition, one row per action. Then run the set review across all five nights and see what surfaces.

★ Step 1 · the timeline with events as ( select partition_hour, landed_at, backfilled_at, datediff('day', :window_start, partition_hour) + 1 as night, landed_at <= partition_hour + interval '60 minute' as fresh, reconciled as correct, row_count >= 0.9 * lag(row_count, 168) over (order by partition_hour) as covered from ops.orders_partition_log where partition_hour >= dateadd('day', -30, current_timestamp) ), bad9 as ( select * from events where night = 9 and not (fresh and correct and covered) ), alerts9 as ( select fired_at, acked_at, safe_state_at from ops.alert_log where pipeline = 'orders' and fired_at between (select min(partition_hour) from bad9) and (select max(partition_hour) from bad9) + interval '2 hour' ) select (select min(partition_hour) from bad9) as first_bad, (select min(fired_at) from alerts9) as paged, (select min(acked_at) from alerts9) as acknowledged, (select min(safe_state_at) from alerts9) as safe_state, (select max(partition_hour) from bad9) as last_bad, (select max(backfilled_at) from bad9) as backfill_complete; -- night 9: 01:00 · 02:04 · 02:11 · 02:40 · 14:00 · 16:35
★ Steps 2 and 3 · impact and readers select count(*) as bad_partitions, count(*) / (720 * (1 - 0.97)) as budget_share_at_97 from bad9; -- 14 · 0.648, two thirds of the 21.6 budget select r.consumer, count(distinct r.partition_hour) as stale_partitions_read from ops.lineage_reads r join bad9 b on b.partition_hour = r.partition_hour and r.source_table = 'orders' and r.read_at < b.backfilled_at group by r.consumer order by stale_partitions_read desc; -- settlement_report 14 · ops_dashboard 14 · seller_payouts 6
★ Step 6 · write the package, then read the set insert into ops.incidents (night, title, first_bad, paged, acknowledged, safe_state, last_bad, backfill_complete, bad_partitions, target, budget_share) select 9, 'upstream outage', first_bad, paged, acknowledged, safe_state, last_bad, backfill_complete, 14, 0.97, 14 / (720 * (1 - 0.97)) from night9_timeline; insert into ops.incident_conditions (night, condition) values (9, 'upstream feed down'), (9, 'no dependency SLO with the source team'), (9, 'runbook stopped at diagnosis'); insert into ops.incident_actions (night, action, owner, due, done_when) values (9, 'dependency SLO with the source team', 'Mara', :the_20th, 'row in ops.contracts'), (9, 'safe-state step in the orders runbook', 'Tomas', :the_15th, 'runbook step 4 exists, dry-run once'); -- the set review: conditions shared across the month's five packages select condition, count(*) as incidents, listagg(night, ', ') within group (order by night) as nights from ops.incident_conditions group by condition having count(*) > 1; -- no coverage check on shard count | 2 | 21, 27

If your timeline query returns nulls, the runbook is not writing its safe-state timestamp, or the backfill job is not stamping backfilled_at. Either is a finding worth more than the package: an incident you cannot time is an incident you cannot shorten.

Homework

Before session 7

40 min
  1. Generate the timeline for each of the other four Harborlight incidents: nights 4, 15, 21 and 27. Note which timestamps come back null and what would have to be logged to fill them.
  2. Compute impact for all five in budget terms at 97 percent. Check that the five shares sum to 35 over 21.6, which is why the budget ran out on night 15.
  3. Write contributing conditions for night 27's partial load, then run the set review and confirm it pairs with night 21.
  4. Take the last postmortem your own team wrote and test each action for owner, date and verification. Count the wishes. Bring the count, not the document.
Check yourself

Three questions

Which of these belongs in the actions section of an incident package?

An action has an owner, a date and a check that returns true or false. A and C have none of the three; they are wishes, and a wish lets the package close with the conditions still true.

Why generate the timeline from the partition log and the alert log instead of writing it from memory?

The value is comparability and truth. Page-to-acknowledgement across five incidents is only a number if all five timelines came from the same query over the same logs.

What does reading the month's five packages together produce that no single package can?

A flapping connector and a partial load look unrelated on their own. The set review finds the property of the system they share, which is the action worth funding first.

Sources covered

What tonight maps to

✓ SRE workbook, Implementing SLOs - an incident measured as a share of the error budget The workbook's worked example is 13 percent of a budget. Night 9 is 14 of 21.6, two thirds, at the 97 percent target.
✓ Blameless postmortems - timeline, contributing conditions, actions with owners Established practice, taught as professional norm. The generated timeline and the owner-date-verification test are the working form of it.
✓ Error budget as 100 percent minus the SLO Reused from session 3 to turn 14 partitions into a budget share.
◐ The postmortem review as a leadership act Leader session 4 owns what the five incidents teach and how to run the review. Here the package is built as its input.
◐ Severity weighting of incidents Every bad partition counts the same in the impact section. A corrupt settlement total is worse than a late one; weighting is the first extension.
· Traces, spans and incident review for AI services Not covered by design. That is the AI observability course; here it is tables, partitions and the people on call for them.

Session 6 cheat sheet

Three sections, fixed orderTimeline, impact in budget terms, actions. The first two are queries.
Six timestampsFirst bad, paged, acknowledged, safe state, last bad, backfill complete. Two logs, one row.
Generated, not rememberedSame fields every incident; cannot be argued with. Memory a week later is fiction with timestamps.
Impact is a budget shareNight 9: 14 partitions, two thirds of 21.6 at 97 percent. Then who read the stale data.
Conditions, not a root causeOutage, no dependency SLO, runbook stopped at diagnosis. All true; none alone the cause.
The action testOwner, date, verifiable when done. Fail one and it is a wish.
Write for the absent readerNames not pronouns, table names not nicknames, impact before narrative.
Read the setFive packages in one table. Nights 21 and 27 share a missing coverage check; one action closes both.