I like the part of the system that only misbehaves under load.

I am Patrick Donahue, a senior software engineer in Seattle. Most of what I write about lives in the unglamorous middle of an application: data models that survive contact with reality, query plans that quietly stop being true as a table grows, and background jobs that fail loudly instead of silently.

Nearly every essay here starts with a real production problem and a number attached to it. A page that counted 800 million rows. 319 N+1 queries across a fleet. A petabyte of media sitting on the wrong storage tier. A goroutine leak with no stack trace, no panic, and no failing test.

Currently open to a full-time engineering role. More about me
What I am deep in right now

Static analysis for bugs that leave no evidence

Goroutine leaks are a genuinely nasty class of bug: no stack trace, no panic, no failing test, just a process that slowly gets worse until someone restarts it. I have been building tools for that, and benchmarking them somewhere honest.

tether

Static goroutine-leak analyzer · Go, x/tools/go/analysis, go/cfg

Treats every go statement as a proof obligation: either find a termination witness, or report it with a concrete escape path. I pointed it at the entire Go 1.25 standard library, which is the only honest way to find out whether a tool like this is real.

  • 91 → 21diagnostics after seven fixed false-positive classes, with no true positives lost
  • 0findings in non-test stdlib code
  • 1real WaitGroup hang found in the database/sql tests
github.com/tachyurgy/tether →

weave

Deterministic interleaving explorer · Go, zero dependencies

The Loom-shaped gap in Go. Runs one goroutine at a time through a stateless DFS with replay, so a concurrency bug that shows up once every ten thousand runs becomes something you can reproduce on purpose and then shrink to its smallest failing schedule.

  • vector clocksrace detection across explored schedules
  • delta debuggingminimises a failing interleaving automatically
  • CHESSpreemption bounding to keep the search finite
github.com/tachyurgy/weave →
Recurring obsessions

The four things I keep coming back to

Written over several years and a lot of production incidents. Grouped by the problem rather than the technology, because the technology keeps changing and the problem does not.

Postgres that stops being the problem

The most common reason a growing app feels bad is a query that was fine at ten thousand rows and is not fine at ten million, and nobody noticed the week it changed.

Reading the bill instead of accepting it

Cloud spend is treated like weather. It is usually a lifecycle rule written years ago against an access pattern nobody re-verified.

Failing loudly, on purpose

Silent failure is the bug that costs you a customer eighteen months later. Idempotency, gap-free logs, and gates that fail closed rather than open.

Recent

Latest writing

All 58 essays →

A little context: I studied history, which nobody hires an engineer for, and which turned out to be four years of practice at the thing this job actually is. You get handed incomplete, contradictory sources and have to produce a defensible account of what happened, while being explicit about which parts you are confident in and which parts you are inferring. That is a debugging session. It is also what reading an unfamiliar codebase feels like.

I have spent the last several years as the senior engineer on a small team, which is a particular kind of education: there is nobody to escalate to. If the database is slow at 2am, that is yours, and you also get to be the one who explains it afterward.

Off the clock I build a lot of small strange things, mostly because the cost of building a small strange thing collapsed, and I host them myself because owning the deploy path teaches you things that reading about it does not. The longer introduction →