Blog
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.
The Debug Line You Disabled Is Still Running
A DEBUG line that never prints still cost me 5,260 ns a call, because the logger checks the level after you've already built the message. That, sync writes on the request thread, and logging every event instead of sampling: three ways logging quietly eats a hot path.
The Priority Queue That Didn't Cut the Line
One queue carried the marketing blast and the OTPs. I added a priority queue and the OTP still waited 3.8 seconds behind the backlog. The culprit was prefetch, not priority. Here's the reproduction.
The Machine That Rehashed Everything
You shard a table across four Postgres machines with hash % 4 and everything gets faster. Then you add the fifth machine and find out that changing the divisor moves four-fifths of your rows, because the row count was baked into the address. The fix is to never hash to machines at all, and the whole design turns on the number you pick before any of that happens.
Want to get blog posts over email?
Enter your email address and get notified when there's a new post!