The feature nobody will admit is dead
An engineer proposes deleting the CSV export button. It has been broken for eight months: the background job that generates the file raises on every run, silently, into a queue nobody watches. Someone greps the codebase for csv_export and finds twelve call sites. The pull request gets a "let's not risk it" and dies in review.
Nobody in that review looked at whether the button was ever clicked. The grep answered "is this code reachable", and the question was "is anyone reaching it". The codebase can't answer that about itself; only production traffic can.
This is the mirror image of the last two courses. The senior courses "Zero-downtime Rails upgrades" and "Large scale migrations: gh-ost" were both about adding and moving things without stopping traffic: new columns, new gems, new frameworks, all landing underneath live requests. Decommissioning is the same discipline pointed the other way, removing a feature, an endpoint, a table or a whole service, with the same constraint. The code has to keep serving requests the entire time you take it apart.
Why deletion is harder than it looks
Adding code is additive by construction: until you flip a switch, the old path still runs, so a bug in the new path costs you nothing you didn't already have. Deleting code removes your ability to answer "did anyone need that" after the fact. You can't canary a deletion the way you canary a feature. Once the code is gone, the only rollback is git revert, and by the time someone notices, the deploy that broke them is three releases behind.
That asymmetry is why decommissioning has its own craft, distinct from writing less code:
| Adding a feature | Removing a feature |
|---|---|
| Ship behind a flag | Prove it's dead first |
| Ramp traffic up | Ramp traffic down |
| Old path still there if wrong | Old path is gone if wrong |
| Rollback = flip the flag back | Rollback = redeploy old code |
"Prove it's dead first" is the subject of the next lesson. For now, notice what killed the CSV export review: the only evidence offered was structural (call sites exist), while the question was behavioral (is anyone calling it). Structural evidence never answers a behavioral question. It only makes removal look risky enough to block, forever, with no way to resolve the doubt.
The four things you decommission, and why they're different operations
This course treats four targets as distinct, because the blast radius and the tooling both differ:
- A feature: a UI flow or behavior inside an app you control end to end. It's the lowest risk, because you can watch its usage directly.
- An endpoint or public API: it has callers you don't control and can't always see. It needs a deprecation window and a public signal, because you can't silently remove what someone else's system depends on.
- A table or column: it has readers you don't control either, such as other tables' joins, reporting queries, admin scripts and a consultant's one-off SQL from two years ago. The database doesn't know who's about to break.
- A whole service: the union of all three, plus its own deploy pipeline, its own on-call rotation and usually a second team's opinion about whether it's dead at all.
Each one gets its own lesson. What's common to all four, and worth carrying through the whole course, is the order: stop writes, then stop reads, then remove the code, then drop the storage. Skip a step (say, drop a column before every reader has stopped reading it) and you get the same outage shape as skipping a step in a zero-downtime migration: a request in flight touches a shape of data that no longer exists.
What you'll work through
- Lesson 2: proving a feature is dead with instrumentation instead of grep, and how long "silent" has to run before it counts as evidence.
- Lesson 3: the strangler fig pattern run backwards, and dark or shadow reads as the way to verify a replacement before you trust it.
- Lesson 4: feature-flag ramp-down, and the trap of the flag that outlives the feature it was guarding.
- Lesson 5: retiring an endpoint or a public API, with deprecation windows, the
SunsetandDeprecationHTTP headers, and what "public" changes about the plan. - Lesson 6: killing a background job or queue safely, and the order of stop writes, stop reads, remove code, drop storage applied to a table and a column.
- Lesson 7: a decommission runbook that ties the course together, and the decision tree for which of the four targets a given removal is.
One thing to carry into lesson 2
The CSV export job that raises on every run is itself a kind of instrumentation, an accidental one. Eight months of failures sitting in a queue nobody watches is evidence that nobody depends on the output. It says nothing about whether anyone still clicks the button expecting something to happen, which is a different failure with a different fix. The next lesson is about building the deliberate version of that signal instead of getting lucky with the accidental one.
Keep going with lesson 2
Instrumentation before deletion
Using production counters and a long enough observation window to prove a feature is unused, since grep cannot.
A free account also gives you the quizzes, your progress and notes, and the discussion.