← Back to list

Before You Rewrite in Go Find the Coordination Tax

Wesley Wei in Programmer’s Career · 2026-05-12 19:31 · 25 claps · 6.6 min read paywalled
#coding #programming #technology #productivity #golang
Open on Medium ↗
Wiki topics: PFI · Personal Finance 💻 · Programming ⏱️ · Productivity

Before You Rewrite in Go Find the Coordination Tax

GitHub’s outages and Ghostty’s exit show why hot paths matter only after the system shape is understood

Go can make a hot path easier to operate. It cannot make a tangled platform simple.

Last year I sat on an incident call for a notification fan-out service. Throughput target was 50,000 events per minute. It had started backing up at 15,000. The first hour of the call was a language argument — someone proposed rewriting it in Go to handle the throughput. The second hour was dashboards. The actual bottleneck turned out to be a downstream auth check with no rate limiter, and upstream retries were amplifying every slow auth call by roughly 5x. We capped retries, added a circuit breaker around the auth path, and the queue drained in twenty minutes. Nothing got rewritten.

I think about that incident every time a developer platform slows down and someone reaches for the language argument. The reach is usually wrong. The first question is almost never which language. The first question is coordination tax — how many downstream systems a single user action wakes up, how retries amplify load, and how many shared services sit in the critical path before a human sees “merged,” “search results loaded,” or “checks passed.”

This frame matters because modern platform failures increasingly come from fan-out machinery rather than from durable storage. A repository can be intact while the user experience is effectively down. The same is true outside code hosting — CI platforms fail in schedulers before artifact stores, chat systems fail in indexing and presence before message durability, and ticketing tools fail in search or permissions before the database itself. The operational question is no longer “did we save the bytes?” It is “how much coordination did we require for a normal action to feel complete?”

1. Four Shapes of Coordination-Heavy Failure

For engineering teams, four failure classes are worth separating early:

  • Queue coordination failures — merge queues, job schedulers, and async workers stop making forward progress even though the underlying data model remains valid.
  • Search and indexing failures — content still exists, but the interface surfaces tied to discovery go dark or become misleading.
  • Retry amplification failures — one slow dependency turns a local problem into a platform-wide traffic multiplier.
  • Shared-control-plane failures — permissions, notifications, webhook delivery, or background orchestration become hidden single points of failure for many product surfaces at once.

This is why “the storage layer is healthy” can coexist with “the platform feels unusable.” Users do not interact with object graphs in the abstract. They interact with review queues, checks, search boxes, bots, project views, and automation hooks. If those layers are tightly coupled and high-fan-out, they become the true availability boundary.

2. Two Different Interventions

Once you look at systems this way, the language discussion becomes narrower and more productive. There are really two different interventions, and they are not interchangeable.

The first is architectural subtraction. Remove unnecessary fan-out, make features degrade independently, cap retries, separate indexing from user-critical paths, or stop routing too many product surfaces through one shared queue or search cluster. If a user action wakes up eight subsystems and only three are essential, cutting the other five usually buys more reliability than rewriting one service. This is what saved us on the notification incident — capped retries plus circuit breaker, no language change.

The second is decomposition. Some paths are hot enough, narrow enough, and operationally important enough to deserve a smaller service with a tighter contract. This is where Go keeps showing up. Not because it is a universal replacement for a monolith, but because a small Go service is easy to benchmark, isolate, capacity-plan, and redeploy under pressure. The real question I ask before extracting is “what can we reason about at 3 a.m. while the incident is still live.” A small Go service with explicit timeouts and a small dependency graph is hard to beat on that axis.

A useful rule of thumb is simple:

  • If the main risk is excess orchestration, reduce coordination first.
  • If one narrow path cannot meet latency or concurrency goals inside the current runtime boundary, extract it.
  • If both are true, do the subtraction first so you do not preserve accidental complexity inside a faster service.

3. GitHub’s April Incidents Through This Lens

GitHub’s late-April incidents are interesting in exactly this systems-design sense.

On April 23, 2026, a merge queue regression affected 658 repositories and 2,092 pull requests. On April 27, an Elasticsearch failure disrupted search-backed surfaces across pull requests, issues, and projects. In both cases, Git data remained intact. What failed was the coordination layer wrapped around the repository.

The taxonomy maps cleanly. A merge queue outage is a queue-coordination failure. A search-backed UI outage is a search-and-indexing failure. Neither primarily says “version control is broken.” Both say the platform’s user-visible control plane is carrying more operational weight than the storage layer beneath it.

This also makes GitHub’s scale comments more interesting than the incidents by themselves. In its availability update, GitHub said the company began a 10X capacity effort in October 2025, then concluded by February 2026 the real target had become 30X current scale. It also described strong acceleration from agentic development workflows beginning in late December 2025. The number I care about most is not a branding metric. It is the fan-out implication behind 1.4 billion commits per month. Every commit can trigger checks, builds, indexing, notifications, permission evaluation, and worker activity. This multiplier is where coordination tax becomes an engineering budget problem.

GitHub also said it is moving “performance or scale sensitive” code out of the Ruby monolith into Go. I read this less as ideology and more as triage discipline. The team is not treating language as the first move. They have already learned enough about the hot paths to know which ones deserve a stricter runtime boundary.

This is where Go earns its keep. The strongest use case is not “replace everything dynamic.” It is “take the narrowest coordination-critical edge and give it a runtime profile you can reason about.” A small extracted service in Go usually has a shape like this:

[embed]

There is nothing exotic here. Explicit timeout. Explicit attempt budget. Explicit backoff. Explicit cancellation. The whole thing fits on one screen and a senior engineer can reason about every failure mode in fifteen minutes. That is the property GitHub is buying when it moves a hot path out of Ruby — not raw speed, but a smaller blast radius and a more legible failure surface.

4. When Go Does Not Help

I want to be honest about the part of this argument that gets oversold.

Go is not a fix for coordination-heavy systems whose problem is genuinely architectural. If the underlying issue is that one shared service has thirty consumers and any one of them can saturate it, rewriting that shared service in Go gives you a faster bottleneck. The bottleneck is still there.

Go is also not a fix for organizational coordination. If the platform’s reliability problems trace back to too many teams shipping into one shared deployment, or too many features routed through one approval queue, the language of the underlying service is irrelevant. The fix is to split the deployment surface or cap the queue, not to swap runtimes.

And Go is not a fix when the bottleneck lives below your code. Database hot rows, lock contention on a shared cache, a single AWS account hitting throughput limits — these are problems the language layer cannot reach. I have watched teams burn six engineer-months rewriting in Go only to land on the same Postgres connection pool exhaustion they started with.

The pattern across all three of these is the same. Reach for Go when the bottleneck is a path you can extract, isolate, and own end-to-end. Do not reach for Go when the bottleneck is something the extracted service has to talk to. The runtime you choose only helps when the runtime is actually where the problem lives.

5. What Ghostty’s Departure Actually Signals

Mitchell Hashimoto’s announcement that Ghostty is leaving GitHub got more attention than the April outages combined. I do not think the migration itself is the spine of the story. The migration is gradual. GitHub will retain a read-only mirror. Ghostty is one project. The world is full of repositories. Hashimoto also noted the decision had been discussed before the April 27 outage, so treating the migration as a reaction to one bad day would overstate the case.

What the announcement signals is something narrower and more useful — a sophisticated user looked at GitHub’s collaboration layer and decided the operational direction was worth voting on with his feet. Hashimoto is not casual about infrastructure. His departure is the kind of signal a senior reliability engineer treats as a leading indicator, not as proof.

In this frame, Ghostty leaving GitHub and GitHub moving Ruby code into Go are the same story told from opposite sides. Both are responses to coordination tax becoming the dominant operational problem. One side is choosing to leave the platform. The other side is restructuring the platform to absorb the load.

The lesson I take is the order. When AI-era throughput increases, do you first simplify coordination, or do you extract the hot edge into something narrower and easier to operate? Most teams need both moves. The hard part is doing them in the right order. Subtraction first, then decomposition, then language. A rewrite that skips the first two steps usually preserves the original problem inside a faster service.

That is the part of the GitHub story most worth keeping. Not “everyone should move to Go.” Just — choose the language only after you have answered the coordination question. The answer changes which language matters, and often whether language matters at all.

References


메타데이터
post_id
90a419fe4ff5
slug
before-you-rewrite-in-go-find-the-coordination-tax-90a419fe4ff5
url
https://medium.programmerscareer.com/before-you-rewrite-in-go-find-the-coordination-tax-90a419fe4ff5
canonical_url
https://medium.programmerscareer.com/before-you-rewrite-in-go-find-the-coordination-tax-90a419fe4ff5
author_url
https://medium.com/@wesley-wei
status
ok
fetched_at
2026-06-14 11:28:49