The Shape of the System

The Gentle Sunset

Every behaviour you ship, even the ones you never meant to promise, becomes somebody's load-bearing wall. You do not get to just knock it down.

In the first few months of 2023 the company that was still called Twitter back then pulled the ladder up on the developers who had built things on top of it. It didn't happen all at once. It came in jolts. January was when the popular third-party apps that a lot of people used to read their timelines just stopped working one day, and there was no warning at all, and it was only later that the company admitted the cut had been on purpose. Then in February they gave a few days' notice that the free tier of the programming interface was going away. That free tier was the thing a long tail of bots and hobby projects and various news and feed readers had quietly been built on. Integrations that had been running for years just went dark, and the people who wrote them found out from their own error logs. None of this was malice. The company had its reasons and it had costs it wanted to cut. But the notice was a matter of days, the changes came in waves that nobody could plan their way around, and the people who'd trusted the platform were given no time to move off it. They didn't get a deprecation. They got a demolition with the tenants still inside.

Now put Python next to that. The language made a breaking jump from its second major version to its third, and the gap between the two really was painful: code did not just port across, you had to do work. But the people looking after it took the patient road. They started signalling the change years ahead of time. They kept both versions alive next to each other for an extraordinarily long stretch, and they pushed the final cut-off date back when it was obvious the world wasn't ready yet, and they didn't actually switch the old version off until the very first day of 2020, which was more than a decade after the new version had first turned up. It was slow enough to be frustrating. And it worked. Huge codebases had the time to plan it out, to run both versions side by side, and to move across one piece at a time. One sunset left a mess behind it and the other one didn't. The thing that made the difference wasn't how hard the change was. It was how long and how honest the goodbye was.

Sitting under both of these stories is a law you don't get to opt out of. An engineer called Hyrum Wright put it plainly enough that it ended up with his name on it, though it was actually a colleague of his, Titus Winters, who stuck the name to it. It goes like this: once you have enough users, every observable behaviour of your system ends up being depended on by somebody, no matter what your documentation says. Not just the features you meant to ship. The exact wording of an error message that somebody out there is scraping. A header you left in by mistake. The precise order that results come back in, which you never said was stable. A side effect you thought of as an implementation detail and nothing more. Ship anything to enough people for long enough and somewhere out there a real, running system is now treating your accident as if it were a contract. Nobody's to blame for this. It's just the gravity that pulls on anything that gets used widely, and what it means is that there's no such thing as a behaviour you can pull out safely just because you never officially promised it in the first place.

Which is why a short removal window is a kind of violence, however politely you word it. The people who rely on you are not sitting there reading your changelog. The code that leans on your behaviour was very often written years back by somebody who has since moved to another team, or another company, or has left the trade altogether. Libraries pin themselves to old versions for the exact reason that old versions don't change. And even when you announce the thing loudly, the announcement only ever reaches some fraction of the people who needed to hear it, and those people all move at their own different speeds. A three-month sunset is a perfectly comfortable deadline if you're a fast-moving startup with one codebase and you deploy continuously. But take an enterprise with a quarterly release train and some legacy system that nobody there fully understands any more. For them three months isn't a deadline at all. It more or less guarantees that they're going to be the wreckage.

The thing that does work is the reverse of how you add stuff, and it has a particular shape to it. When you add a feature you stand up the new path and you eventually make people use it. Removing one is that run backwards, and done slowly. So first you stand the new path up next to the old one, and you keep the old one fully working the whole time. Not crippled. Not quietly degraded. Working. You put out a deprecation warning, which is a signal the user can actually see in their logs and their tooling without anything breaking on them, so the message gets to them well before the consequence does. You publish the cut-off date early, in months and not weeks, six of them at the very least and a whole year if the thing is deployed all over the place. Then, with both paths still running, you watch. You measure how many people are still on the old one. You get to see the migration stall, and you can go and find out why, and chase down the stragglers, and move the date if the numbers are telling you that you have to. The old path doesn't disappear because a calendar said so. It disappears once the calendar and the actual evidence finally line up.

And the last step is the one that people in a hurry skip over. You remove the old path only once you've confirmed that nobody is still standing on it. If somebody still is, then you've got two honest options. You extend the window. Or you break them on purpose, but knowing exactly who it is and how badly it'll hurt, with that call made deliberately and written down somewhere, rather than it being something a stranger finds out about when their integration suddenly goes dark. The one thing you don't get to do is pull it blind and then learn who was depending on it from the angry tickets coming in, because by that point the damage is already done and you've spent the trust. Breaking a contract you didn't know you had costs something, and that cost always lands on somebody who trusted you, and it's always bigger than what it would have cost you to just take the slow way out.

A behaviour you never meant to document is still somebody's production dependency. None of which is a reason to never remove anything, because a system that can't ever shed its past will in the end collapse under the weight of all of it. It's a reason to remove things the way you'd defuse something delicate. You say it early. You leave both wires live while everyone gets themselves across. And you keep watching the old one until it's carrying nothing at all, and that's the point where you finally cut. A sunset isn't the instant you flip the thing off. It's the long, deliberate, slightly boring stretch of light before that, the bit that gives everyone the time they need to get home.


In the manifesto, this is tenets (XV) and (XX).

Sources

  • [Python 2020] Python core team, "Sunsetting Python 2". Python.org, 2020. https://www.python.org/doc/sunset-python-2/. Signalled in 2008, extended in 2014, and finally sunset on 1 January 2020: the patient deprecation as the worked example; tenets XV, XX.
  • [Sato 2014] Danilo Sato, "ParallelChange (expand and contract)". martinfowler.com, 2014. https://martinfowler.com/bliki/ParallelChange.html. Expand, migrate, contract: run both paths while consumers move across, then remove the old one; tenet XX.
  • [Wilde 2019] Erik Wilde, "The Sunset HTTP Header Field". RFC 8594, IETF, 2019. https://www.rfc-editor.org/rfc/rfc8594. A standard way to signal a deprecation and its date before the consequence lands; tenet XV.
  • [Wright 2012] Hyrum Wright (named by Titus Winters), "Hyrum's Law". c.2012; in "Software Engineering at Google", 2020. https://www.hyrumslaw.com/. With enough users, every observable behaviour of your system becomes a depended-on contract; tenet XV.

One of a series of field notes on building software for the way minds actually work: tired, distractible, ordinary, and now partly machine. They all lead back to the manifesto behind them, The Shape of the System.