Observability with OpenTelemetry: What's Worth It and What Isn't

I’ve adopted OpenTelemetry on three different services over the last two years, with varying success. This post is the honest version of what I’ve learned: where it pays off, where the friction lives, and what I would do differently next time. The three signals, ranked by effort vs payoff OTel covers traces, metrics, and logs. They are not equally easy to adopt and they don’t deliver equal value. Traces — high payoff, moderate effort. The first time you see a flame graph showing exactly where a slow request spent its 800ms, all the instrumentation work feels worth it. Auto-instrumentation libraries cover most of what you need (HTTP, database, Redis, gRPC) so the upfront cost is mostly: install SDK, configure exporter, deploy. Worth doing first. ...

March 8, 2026 · 4 min · 825 words · John

Postgres Performance Tuning: The First Five Things I Check

When somebody hands me a Postgres database and says “it’s slow,” I work through the same checklist almost every time. None of these are exotic. They’re the boring, high-leverage things that solve maybe 80 percent of the cases I see before anyone has to think hard. 1. EXPLAIN (ANALYZE, BUFFERS) before anything else Theories about why a query is slow are mostly worthless. Run the query with EXPLAIN (ANALYZE, BUFFERS) and read the output. ...

January 12, 2026 · 4 min · 784 words · John

Kubernetes NetworkPolicy: A Practical Guide to Default-Deny

The first time I tried to enforce NetworkPolicy in a real cluster, I broke DNS for an entire namespace and spent the next forty minutes wondering why every pod was returning i/o timeout. This post is the guide I wish I had read first. The mental model A NetworkPolicy is a label-selector-driven firewall rule. It only does two things: Selects pods by label (in a single namespace). Specifies allowed ingress, egress, or both for those pods. Three rules that took me too long to internalize: ...

November 5, 2025 · 4 min · 751 words · John

A Worker Pool That Actually Shuts Down: Go Concurrency Patterns Revisited

Worker pools are one of those patterns that looks trivial in a blog post and becomes surprisingly difficult in production. The version I keep coming back to is built around three rules: a single owner, explicit cancellation, and a bounded queue. This post walks through the version I actually paste into projects. The naive version Most worker pool examples on the internet look like this: jobs := make(chan Job, 100) for i := 0; i < 8; i++ { go func() { for j := range jobs { process(j) } }() } This works for the happy path and falls apart everywhere else. There’s no way to wait for the workers to finish, no way to stop them early, and no way to know if process panicked. If the producer crashes, the workers stay alive forever. ...

September 22, 2025 · 4 min · 727 words · John

Hello World — Why I'm Starting This Blog

After almost a decade of writing code professionally, I have built up a fairly large pile of half-finished notes, throwaway gists, and Slack messages to my future self. Most of them never see daylight again. This blog is an attempt to fix that. Why now Earlier this year I rebuilt a service that had been quietly accumulating tech debt for three years. By the time I was done I had read through thousands of lines of code I had written years ago and barely recognized any of it. There were comments like // TODO: figure this out later next to a perfectly reasonable explanation that I clearly knew at the time but no longer remembered. I wished, more than once, that I had written things down somewhere I would actually be able to find them. ...

August 15, 2025 · 4 min · 647 words · John