Skip to main content

Cost and Signal Worksheet

You've found a way the system could break. The first question is whether you can design it out. The second is what goes on top, because a guard is code too, and nothing is impossible until you have checked.

First: can you prevent it?

What it meansWhat it costs, forever
PreventA guard, a constraint, a type, a foreign key that refuses the bad stateA branch that must be read and maintained, and that is only a guard if it actually holds

Then: which layers go on top? Tick every one it deserves. The layers stack on each other and on Prevent, so you can pick several.

What it meansWhat it costs, forever
TestAn assertion that fails before it ships, including a test that the guard holdsAuthoring, plus runtime on every run, plus the time spent chasing false failures
DetectA probe, an alert, and a playbook for when it firesInstrumentation, threshold tuning, and somebody carrying the pager
RecoverA way to undo it: a backup you have restored from, a down migration, a flag you can turn offStorage, drills, and the discipline to run the drills
AcceptNone of the above, on purpose. Write down why it's survivableNothing, if you were right. The incident, if you were wrong

Blast radius decides how deep the stack goes. A failure that can lose a semester of grades gets a guard, a test of the guard, a backup, a restore drill, and an alert when the backup doesn't run. A failure a human can fix by hand in a minute gets one sentence in the pull request.

Every layer needs its own evidence that it works. A backup you've never restored from doesn't count as a layer yet. A constraint you have never tried to violate might be on the wrong column.

Accept is a real engineering decision when you can defend it. It's only legitimate when you can say why: the blast radius is small, the failure announces itself, a human can fix it by hand, or the cost of the alternative exceeds the cost of the failure. "I didn't get to it" isn't the same sentence as "I decided not to, and here is the reason." Accept is also the only choice that excludes the others, since it means zero layers.

In class on Thu Sep 24: print the one-page worksheet, one per pair, and bring a pen. The sheets are collected and scanned, and become test data for the paper-exam grading project. They're used inside this course only, and are never published or shared outside the class. Say so in the room if you would rather yours were not used.

The four questions​

For each candidate failure, answer these before you decide what it gets.

  1. How bad is it when it happens? Ask about severity here, not likelihood. One student's grade is wrong, or every course loses the gradebook.
  2. How would you find out? Immediately from a crash, within an hour from an alert, or in November from a student.
  3. Can you undo it? A flag you can flip in thirty seconds is a different risk from a migration that has already run. If the answer is no, ask whether you could build a way.
  4. What does the fix cost forever? Count what it costs the next person who reads the file, beyond what it costs to write.

Likelihood is the question students reach for first and it is the least useful of the four. A one-in-a-thousand failure that silently corrupts grades outranks a daily annoyance that announces itself.

The five candidates​

All five are ways a column-groups feature could go wrong. You just spent two weeks building one, so you already know everything you need to judge them. Where a candidate depends on a choice you made in your own migration, it says so.

A. The backfill puts a column in the wrong group, and it looks plausible​

Your backfill reads the existing columns in every course and assigns each one to a group. In one course, with a slug shape the seed doesn't have, one column ends up in a neighboring group instead of its own. The gradebook renders, every header is there, and the grouping looks like a choice somebody could have made.

Why it happens: the heuristic you replaced has special cases (assignment-<type>-*, the sort_order contiguity check), and a backfill that reimplements it can disagree with it on exactly the inputs nobody seeded.

B. The gradebook is fine on the seed, and crawls for our biggest course​

The cs4535 seed is one small class. CS 2100 is 500 students and 400 columns, and it is the course that uses Pawtograder hardest. The gradebook page loads groups, columns and scores together, and on that course it takes long enough to load that instructors give up on it.

Why it happens: a query that is cheap for 40 columns can be expensive for 400, especially if the group lookup runs once per column, or an RLS policy on your new table does extra work per row.

C. Two instructors rename the same group at the same moment, and one rename is lost​

Two instructors in the same course both open the group editor and rename the labs group within the same second. Both see their save succeed. The group ends up with whichever name was written last, and nobody is told the other rename was overwritten.

No columns move and no scores are affected. For this to happen at all, two people have to edit the same group's name at the same moment.

Why it happens: each save writes the name without checking whether it changed since the editor loaded it. Closing that gap means optimistic locking (a version number checked on every write) and a way to show the conflict to whoever lost.

D. Deleting a group deletes its columns, and every student's scores in them​

An instructor deletes a group they no longer want, expecting its columns to become ungrouped. Instead the columns go with it, and so does every student's score in every one of those columns.

Why it happens: it depends on the foreign key you wrote. If the column's reference to its group is ON DELETE CASCADE, deleting a group deletes its columns, and the scores cascade from there. Go and check what yours says.

E. Instructors open the new group editor, get confused, and give up​

You ship create, rename and reorder for groups. Instructors open the editor, don't understand what dragging a column between groups will do, and close it without changing anything. Nothing errors, and every test passes.

Why it happens: tests check that the editor does what you built. They can't check that what you built is what an instructor expected.

An example row​

The candidate: an instructor can create a group with an empty name.

QuestionAnswer
How bad?A header with no text above some columns. Confusing, not dangerous.
How would you find out?The instructor who did it would see it straight away.
Can you undo it?Yes, trivially: rename it.
Prevent?Yes. One CHECK (length(trim(name)) > 0) in the migration.
ThenNothing more. The failure is visible and harmless, so the guard doesn't need a test, an alert or a backup behind it.

The cost of the fix decided this row. Severity was low, and likelihood didn't come into it. When prevention is nearly free and lives in the schema, prevent. And because the blast radius is small, stop there.