Patrick Donahue

@levelbrook.bsky.social

Senior backend engineer. Ruby on Rails, Go, Postgres. I write about the part of a system that only misbehaves under load. 58 essays, plus the tools and demos behind them: levelbrook.com Open to contract work.

Claude Code AGENTS.md gotcha that isn't the telemetry bug: a CLAUDE.local.md in your repo or any folder above it counts as a CLAUDE.md, so AGENTS.md stops loading for you and nobody else. Your global ~/.claude/CLAUDE.md doesn't count. The session start line says which file won.

PgBouncer 1.26 is out with two pre-auth DoS fixes, so upgrade tonight if untrusted clients can reach it. The quieter change: search_path is tracked by default now, but only on PG 18+. On 17 and older, SET search_path in transaction pooling still leaks to whoever gets the connection next.

Where Postgres keeps a row lock: FOR UPDATE writes the locker's xid into t_xmax on the page, and conflicts with the FOR KEY SHARE every FK check takes. Rails: with_lock takes a string, so with_lock("FOR NO KEY UPDATE") stops blocking child inserts. boringsql.com/posts/row-locks-on-the-page/

Tin, PlanetScale's new Postgres text index, is cloud-only, but the design note is worth reading: postings are raw ctids, so a page bitmap is 256 bits and ANDing two terms is one AVX2 op per 256 pages. Build on 150M docs: 8m10s vs 2h09m for GIN. planetscale.com/blog/introducing-tin

The page that counted 800 million rows on every load. Sixteen unrelated-looking timeouts in the error tracker were one COUNT(*) template from four controllers. The fix was not a faster query: aggregate columns plus a daily-buckets table. Write-up: levelbrook.com/writing/count-queries-on-800m-rows/

The RL-trained query planner post (4B model, 44.7% lower latency on the Join Order Benchmark) ships its plans as plain pg_hint_plan comments: Leading() and HashJoin(). Older lesson underneath: join ordering is NP-hard, the planner loses on correlated columns, and CREATE STATISTICS fixes most of it.

RubyGems 4.0.21 is out and three of the small items are the ones to read this week: gem extraction now normalizes absolute symlink targets, Bundler refuses a redirect that downgrades https to http, and an empty CHECKSUMS entry no longer triggers a local resolve. gem update --system #Ruby

Plan cache, for Rails people: ActiveRecord prepares statements on Postgres by default. The first five runs get custom plans, then Postgres goes generic if that looks cheaper. tenant_id = $1 with one whale tenant is where run six slows. Fix: database.yml variables: plan_cache_mode: force_custom_plan

Rails footgun of the day: the commented-out default in ApplicationJob, discard_on ActiveJob::DeserializationError, discards more than deleted records. Argument deserialization rescues everything, so a connection drop while locating the GlobalID becomes a quiet discard. Check error.cause. #Ruby

The RubyGems story this week is a registry problem, but your side of the fix has shipped since Bundler 2.6: bundle lock --add-checksums writes a CHECKSUMS section into Gemfile.lock, and every later install raises ChecksumMismatchError if a gem's bytes change. Pin the bytes, not the version. #Ruby

Exactly-once is a lie. Any Sidekiq job can run twice: a deploy, an OOM or a Redis blip lands between the work and the ack, and Sidekiq re-runs it. What you can build is exactly-once effect. The patterns, in order: levelbrook.com/writing/sidekiq-idempotency-and-reliability/ #RubyOnRails #Sidekiq