Latest Blog Posts
Notes from production. Backend, databases, distributed systems, and the failure modes you only see at scale.
Postgres Won the Database War. Now What?
Postgres passed MySQL on the Stack Overflow survey. 'Postgres for everything' became the default. Where that gravity helps, where it quietly stops, and my rule.
Read more BackendYou're Running Kafka for Three Topics
Kafka is a commit log for high-throughput streaming. Three topics at 10 msg/sec is not that. The operational tax you didn't price, and the tool that fits.
Read more BackendREST vs GraphQL Is Over. You're Now Running Both, Badly.
REST vs GraphQL got declared a tie: use both. Nobody costs that out. Two contracts, the HTTP caching you lost, a leaky BFF, and the N+1 that just moved.
Read more DatabaseDuckDB Ate Your Analytics Pipeline and That's Fine
Most 'big data' analytics is tens of gigabytes, not terabytes. DuckDB runs it on one node in seconds, no cluster. When single-node wins and where it bites.
Read more ToolsThe RAG Pipeline That Confidently Made Things Up
A RAG assistant gave a confident, well-cited answer that was pure fiction. Why retrieval success is not grounding, and why the eval set is the only real fix.
Read more BackendServer-Sent Events Are Back. You Should Use Them.
Server-Sent Events made a quiet comeback because of LLM streaming. SSE vs WebSocket, the HTTP/1.1 connection trap, and the cases where SSE is the right call.
Read more BackendWhy Your Distributed Lock Doesn't Lock
Distributed locks don't provide mutual exclusion. Fencing tokens, GC pauses, clock drift, and why the lock you wrote is actually a polite hint at best.
Read more ToolsThe Day Our LLM Bill Hit $40k
A weekend, a retry loop, and an Anthropic API key. How $40k of LLM cost happened in 60 hours, and the five-line policy that would have prevented all of it.
Read more ToolsClaude Code vs Cursor: Six Months of Both
Six months running Cursor and Claude Code side by side. Where each one wins, where they don't compete, and the design choice that made me drop Cursor.
Read more Databasepgvector at 10 Million Rows Is a Different Animal
pgvector is great at 100k rows. At 10 million it's a different animal. IVFFlat vs HNSW, storage math, the quantization escape, and what actually breaks.
Read more ToolsYour AI Agent Isn't Broken. Your Evals Are.
Your AI agent isn't broken in some mysterious way. You just don't have evals. Why 'works on the demo' is the most expensive sentence in your AI roadmap.
Read more BackendThe Kafka Consumer Group That Stopped Consuming
A Kafka consumer group can stop consuming while every metric looks healthy. Rebalance storms, max poll timeouts, stuck partitions, and how to actually diagnose.
Read more BackendThe Postgres Index That Never Gets Used
Postgres indexes accumulate. Every perf push adds one. Almost no team removes any. How to find the unused ones and why their write cost is the bigger problem.
Read more ThinkingAI Code Review Is Mostly Noise
AI code reviewers are the hot dev-tool category of 2026. After months of real use on a review queue, the signal-to-noise ratio is bad. Here are the numbers.
Read more BackendThe Endpoint That Always Returns 200
REST APIs that wrap every error in HTTP 200 break retries, caches, load balancers, circuit breakers, and observability. The status code is part of the contract.
Read more DevopsDNS Is Always the Answer
It really is always DNS. TTL caches, resolver storms, NXDOMAIN under load, split-horizon traps, SNI mismatches. The actual failure modes behind the meme.
Read more BackendOpen Session in View Is Spring Boot's Quietest Footgun
Spring Boot ships with Open Session in View on by default. Lazy loading from controllers, hidden N+1s, JDBC connections pinned for the whole HTTP request.
Read more BackendFour Spring Boot 4 Features That Actually Change Your Code
Spring Boot 4 release notes are long. Four features change how you actually write code: API versioning, HTTP service clients, virtual threads, RestTestClient.
Read more BackendThe Long Transaction That Ate Your Postgres
An idle transaction pins the xmin horizon. Autovacuum cannot reclaim anything newer than it. Tables bloat. Queries slow. Here is the fix every install needs.
Read more ThinkingYour AI Coding Speedup Is Not What You Think
AI coding tools promise a 10x speedup. After a year of daily Claude Code use, the real number on a real codebase is closer to 1.5x. Here is what I measure.
Read more BackendSagas Are Not Transactions
Sagas replace ACID transactions with compensation actions, not rollbacks. Intermediate states are visible to other services, and compensations can fail too.
Read more BackendChecked Exceptions Were a Mistake and Spring Proved It
Checked exceptions force callers to acknowledge errors, not handle them. Spring's unchecked hierarchy and the @Transactional rollback default show the cost.
Read more BackendYour Spring Bean Is Not What You Think It Is
Spring's default bean scope is singleton. The bugs appear when a service holds mutable state, a scoped bean is misused, or ThreadLocal cleanup is skipped.
Read more BackendYour Equals and HashCode Are Wrong
Hibernate entities in a Set stop being findable after you persist them. equals and hashCode based on a null id change the moment the database assigns a value.
Read more BackendYour JWT Is Not a Session
JWTs cannot be revoked, permissions inside them go stale, and clocks drift. The failure modes that appear when you treat a signed token like a session.
Read more BackendHibernate's ddl-auto Is Not a Migration Tool
ddl-auto: update silently adds columns and never drops them. What it does to your schema, why it fails on renames, and how to safely replace it with Flyway.
Read more BackendYour Replica Is Lying To You
Read replicas trade staleness for throughput. Replication lag, read-your-writes, and the staleness window nobody tracks: these are where things actually break.
Read more PracticesThe Test That Passes When Prod Is Broken
Your unit tests are mostly testing your mocks. When the mock drifts from the real dependency, tests pass and prod breaks. The fix is harder than it looks.
Read more DevopsYour Logs Are a Pile, Not a System
Log levels exist for a reason. Most codebases ignore the reason, log everything at INFO, then wonder why the logs are useless during an actual incident.
Read more BackendUTC In the Database Was the Easy Part
Storing UTC is the easy part. Display, recurring events, DST, date-only fields, and scheduled jobs: the real bugs live in the conversion layer, not the storage.
Read more BackendYour Async Code Is Still Single-Threaded
Async lets one thread do more I/O. It does not let one thread do more CPU. Most async-related performance disappointments come from confusing those two.
Read more BackendTwo Transactions Walk Into a Lock
Deadlocks are not exotic. They are predictable consequences of lock order. Here are the patterns, the fixes, and the eleven characters that ended one outage.
Read more PracticesNobody Reads a 1,000-Line Diff
The math on PR review quality is brutal. Past 400 lines, defect detection collapses, approvals get rubber-stamped, and your review process is theatre.
Read more BackendThe Cache-Control Header You're Probably Ignoring
Most developers set max-age and call it done. The directives that matter for CDN behavior, revalidation, and stale content are all sitting there unused.
Read more BackendWhy Your Service Slows Down at 9am Every Day
Your service slows every morning for the same five reasons: JVM warmup, cold caches, pool growth, clustered crons, deployment timing. Here's how to fix each.
Read more BackendSpring Boot Auto-Configuration Is Magic Until It Isn't
Spring Boot configures your app without a line of config. Then it configures something you did not want. Here is how the mechanism works and how to control it.
Read more BackendCQRS Sounds Fancy Until You Have to Debug It
CQRS separates reads from writes but not bugs from confusion about which side caused them. Here is when the pattern helps and when it just adds complexity.
Read more BackendThe Composite Index Nobody Can Read
Your composite index covers every column but EXPLAIN still shows a full scan. Column order, not column presence, determines whether PostgreSQL uses the index.
Read more BackendThe Hibernate Query You Didn't Write
Hibernate writes SQL you never see. Three repository lines execute a 2,100-character query that is usually worse than anything you would have written by hand.
Read more BackendDatabase Isolation Levels Are a Contract, Not a Dial
Isolation levels define which anomalies you tolerate, not how much correctness you get. The SQL standard and what databases implement diverged decades ago.
Read more BackendThe NULL Trap
NULL is a three-valued logic system that corrupts aggregations, breaks NOT IN subqueries, and hides rows from WHERE clauses. It is not just a missing value.
Read more BackendThe Thundering Herd Problem
Cache stampedes, retry storms, reconnect floods: three failure modes with the same root cause. Synchronized behavior under load amplifies failures every time.
Read more BackendEXPLAIN ANALYZE Lies to You
PostgreSQL's EXPLAIN ANALYZE tells you the plan looks fast. It doesn't tell you the timing is cached, the estimates are stale, or the cost isn't milliseconds.
Read more BackendDatabase Partitioning: The Decision You Can't Undo
Range vs hash partitioning, hot spots, and the re-partitioning trap. Partitioning looks like a scaling win until you find out you cannot undo the choice.
Read more BackendConnection Pool Tuning: A Practical Guide
HikariCP's defaults look sensible until your app hits production load. Here's how to actually size your connection pool using Little's Law, not guesswork.
Read more BackendWebhook Reliability: The Lost Art
Webhooks break predictably: duplicate events, missed deliveries, retry storms. Here is what it actually takes to build receivers that hold up in production.
Read more Backend@Transactional Is Not Magic
@Transactional looks simple until you hit self-invocation, wrong exception types, or silent failures on private methods. Here's what it actually does.
Read more BackendTransactions Don't Fix Race Conditions
Wrapping code in a transaction doesn't make concurrent operations safe. Here's what transactions guarantee and what race conditions they let slip through.
Read more PracticesGood Enough Is a Strategy
In engineering, perfectionism is often procrastination disguised as craftsmanship. Shipping an 80% solution and iterating beats a perfect solution shipped late.
Read more BackendSoft Deletes Are a Trap
Adding is_deleted to your tables feels harmless. Here's what it actually costs: broken constraints, query pollution, index bloat, and cascading confusion.
Read more BackendThe Java Terminology Survival Guide
Java EE, Jakarta EE, javax, SE, JDK, JRE, OpenJDK, Spring Boot, Spring MVC. A plain-language guide to every confusing name in the entire Java ecosystem.
Read more BackendThe Outbox Pattern: Reliable Events Without Two-Phase Commit
Reliable event publishing alongside database writes is harder than it looks. The transactional outbox pattern solves it without distributed transactions.
Read more BackendEvent Sourcing Sounds Better Than It Is
Event sourcing promises auditability, time travel, and decoupled systems. The operational complexity arrives later, and most teams are not ready for it.
Read more BackendRate Limiting Is Harder Than It Looks
Token bucket, sliding window, fixed counter: rate limiting algorithms all sound simple until you actually implement them correctly across distributed systems.
Read more BackendDistributed Transactions Are a Lie
Why two-phase commit fails in production distributed systems, and what engineers actually use instead: sagas, the outbox pattern, and eventual consistency.
Read more PracticesPremature Abstraction Is Worse Than Duplication
DRY is right, but not yet. Three identical code blocks are better than one wrong abstraction that fights you for months. Wait until the pattern is obvious.
Read more BackendThe Database Is Not Your Message Queue
Polling a status column every few seconds works until it does not. Here is why your database makes a terrible message queue and what to reach for instead.
Read more DevopsMonitoring Is Not a Dashboard
Real monitoring is not a Grafana dashboard. It is knowing which questions to ask, which signals answer them, and what to do when the answer is unexpected.
Read more ThinkingThe Senior Engineer's Job Is to Say No
Knowing what not to build is more valuable than knowing how to build it. The hardest skill I learned as a senior engineer had nothing to do with code.
Read more BackendCaching Is Easy Until It Isn't
Redis, in-memory, CDN: caching feels simple until invalidation ruins your week. Here's how each caching layer bites you and what I learned the hard way.
Read more DevopsThe Deploy That Took Down Friday
Friday deploys have a reputation for a reason. Here's why they go wrong, what guardrails actually help, and when it's okay to ship on a Friday anyway.
Read more BackendConnection Pools: The Thing You Never Think About Until Production Burns
Connection pools sit quietly until they break. Here is what happens when they fail, the warning signs to watch, and how to catch it before production burns.
Read more ThinkingHealthcare AI Has an Engineering Problem
Healthcare AI fails not because the models are bad, but because the surrounding software is. A software engineer's take on why it is harder than it looks.
Read more BackendDatabase Indexes Explained for People Who Keep Forgetting
You've read about database indexes before. You've forgotten most of it. Here's the practical guide you'll actually remember, with PostgreSQL examples.
Read more BackendSpring Boot Security Is Hard and That's Okay
Spring Security has a brutal learning curve. The filter chain is confusing, the docs assume too much, and 403 errors haunt your dreams. But it's worth it.
Read more BackendThe Monolith That Works Fine, Thanks
Monoliths aren't a sign of technical immaturity. Most teams don't need microservices. Here's why a well-structured monolith is often the smarter choice.
Read more ThinkingWhy Most Technical Debates Don't Matter
Tabs vs spaces. REST vs GraphQL. Monolith vs microservices. Some technical debates matter, but most don't. Here's how I tell the difference after a decade.
Read more ThinkingVibe Coding Is Not Engineering
Vibe coding has its place. But the gap between prompting an AI until something works and actually engineering reliable software is wider than people think.
Read more ThinkingI Spent a Week on Moltbook, the Social Network Where Only AI Agents Can Post
I browsed Moltbook for a week to see what AI agents actually talk about when humans can't participate. Some of it was fascinating. A lot of it was weird.
Read more ToolsI Gave My AI Agent Access to My Life. Here's What Happened
I connected OpenClaw to WhatsApp, iMessage, and my browser with scheduled heartbeats. Here is what living with an always-on AI agent for a week looks like.
Read more MobileSwiftUI vs Jetpack Compose: A Side-by-Side Comparison From Someone Who Uses Both Daily
I maintain the same app on iOS and Android. Here's how SwiftUI and Jetpack Compose actually compare when you ship features across both platforms weekly.
Read more ToolsDebugging iOS Memory Issues from Your AI Chat Interface
I built an MCP server that lets Claude inspect iOS simulators from the chat window. Crash analysis, memory risk scoring, log streaming, and leak detection.
Read more ToolsI Use Slack to Boss Around My Local Claude Code Terminal (And It's Glorious)
How I wired up Slack as a remote control for my local Claude Code terminal. I can send coding prompts from my phone, from a meeting, or anywhere else.
Read more BackendWhy Component-Based Structure Beats Traditional Package-by-Layer in Java
Why organizing Java code by feature instead of by layer (controller/service/repository) gives you better modularity, easier navigation, and real encapsulation.
Read more ToolsHow I Use MCP Agents, Skills, and Subagents to Build Software Faster
My actual workflow with Claude Code's MCP servers, custom agents, and skills across Spring Boot, iOS, and Android. Real examples from production code.
Read more ToolsHow MCP Servers Supercharge My AI Development Workflow
The four MCP servers I use daily with Claude Code: Firebase, Notion, Context7, Greptile. What each one does, how to set them up, how they work together.
Read more ThinkingThe Dunning-Kruger Effect: Why Incompetence Feels Like Confidence
People with limited skill overestimate their ability while experts underestimate theirs. Here is how the Dunning-Kruger effect shows up in engineering teams.
Read more PracticesGit Best Practices for Modern Engineering Teams
A visual guide to Git branching: staging vs production setups, GitFlow vs Trunk-Based Development, and the commands that matter, with diagrams and examples.
Read more ToolsHow I Use Claude Code Terminal to Supercharge My Development Workflow
Patterns from months of daily Claude Code use: parallel tasks, MCP integrations, git workflow automation, and tips for getting consistent real-world value.
Read more