Blog
The Flame Graph That Found 25 Years a Day
A plain walkthrough of what a flame graph actually shows, why Java was invisible to Linux perf for years, and how Netflix read one graph well enough to hand back 13 million minutes of CPU time a day.
The CPU Number That Didn't Move
I built the classic unanchored-regex CPU bug on purpose and profiled it with async-profiler. Process CPU load barely moved, 9.9% to 10.05%, while throughput dropped 8x, 1.86M lines/sec to 14.79M. top would have told me nothing was wrong.
The Counter That Lost 336 Of Its 400 Increments
Eight workers incremented a Postgres counter 400 times under READ COMMITTED. Every transaction committed, not one error was raised, and the final balance was 64. The same workload at REPEATABLE READ lost nothing silently, it just told me about all 319 conflicts instead. Then write skew broke an invariant in 200 out of 200 trials at REPEATABLE READ and 0 out of 200 at SERIALIZABLE.
The Climb That Never Comes Back Down
A heap leak and a healthy busy service can look identical on a memory graph. The only number that separates them is what garbage collection gives back. So I ran four JVMs into the ground under capped limits and watched the post-GC live set climb to the ceiling, the GC-overhead tripwire fire, and two more walls, metaspace and off-heap direct buffers, that no amount of heap tuning would have fixed.
ORM vs Stored Procedures Was the Wrong Question
Everyone benchmarks ORM against stored procedures and reports a winner. I tried to run that fight fairly on Postgres and it kept dissolving into four different fights. When the generated SQL is identical the two are within 125 microseconds of each other. The real gaps have names, N+1, object hydration, parameterization, and none of them are "ORM vs stored procedure."
The p99.9 That Only Spiked While We Were Scaling Up
p50 and p99 stay flat while p99.9 rises during a replica's full sync. I built the harness to measure it for real, went looking for the classic Transparent Huge Pages explanation, and it genuinely wasn't there.
What a Span Actually Costs
OpenTelemetry instrumentation is not free, but the SDK is rarely where it hurts. A span costs a few microseconds and each attribute adds a bit more, which almost never matters. What matters is the span processor: SimpleSpanProcessor exports on the request thread and pays the full collector round trip on every single call, and the fix is a batch processor plus head sampling. Here is the reproduction, in microseconds.
Sampled to Keep, Dropped Anyway
Tail sampling in the OpenTelemetry Collector buffers whole traces in memory until a decision timer fires, so the memory it needs is rate times decision_wait, and at high traffic that number is bigger than the container. When the buffer overflows it drops the oldest traces first, which are exactly the error traces you turned tail sampling on to keep. Here is the reproduction, including the OOMKill loop.
Your Secret Variable Is Only Hiding From You
Marking a Postman variable "secret" masks it with dots in the UI. It does not encrypt it, and it does not stop the app from resolving it to plaintext and syncing it to Postman's servers. A researcher caught the client shipping resolved secrets to analytics endpoints. Whether that exact bug still lives, the structural truth does, because a cloud tool that resolves your secret already has it.
It Was Never a Breach
Someone crawled 30,000 public Postman workspaces for a year and pulled out live GitHub, Razorpay, and Okta credentials. Nothing was hacked. The tool worked exactly as designed. The real bug is that Postman is a credential store wearing a testing-tool costume, and opt-in security protects nobody who needs it.
Too Many Clients Already
Traffic doubles, the app opens more connections to Postgres, and the database starts turning callers away with FATAL sorry too many clients already. The query was never the problem, the connection was. Same load through PgBouncer in transaction mode answered every caller with a tenth of the backends. Here's the reproduction.
More Connections, Slower Reads
A read-heavy database falls behind, so you add app instances and raise max_connections to let more callers in. Throughput barely moves and the p99 goes to 1.7 seconds. The same 512 callers over a pool of 8 backend connections answered at 42ms. Here's the reproduction.
Want to get blog posts over email?
Enter your email address and get notified when there's a new post!