Writing
Technical writing on
backend engineering.
27 long-form articles on Node.js, PostgreSQL, AWS infrastructure and system design — all of it distilled from running a production SaaS at 20,000+ user scale, not from tutorials. The recurring themes: making the server the authority, designing for at-least-once delivery, and keeping databases fast as load climbs. Most of it comes out of the systems I've shipped.
Topics: Backend (14) · DevOps (3) · System Design (10) · Subscribe via RSS
Why Was My Postgres Trigger 100× Slower in Production? It Re-Planned Its Upsert on Every Call
A settlement trigger shipped at lunch peak turned 3 ms order updates into 400 ms ones and took 49% of database time. I blamed memory for three hours. The real cause: PL/pgSQL never switched the trigger's upsert to a generic plan, so Postgres re-planned it on every call — 65 ms of planning for 5 ms of work. The fix was one line: plan_cache_mode = force_generic_plan.
PGRST116 Means Zero or Many Rows: How One Double-Tap Grew 2,008 Duplicate Bookings
A meal-booking check used supabase-js .single() and treated PGRST116 as 'no row yet'. PostgREST returns the same code for more than one row, so after a single double-tap race every later tap inserted another booking. 26 months, 2,008 extra rows, one person with 25 in a day. Why check-then-insert without a unique constraint breeds duplicates, and the order to fix it in.
How Old Is an Offline Event? Measure It When It Was Queued, Not When It Was Delivered
An edge agent between biometric terminals and a backend buffers scans through outages. Every time-based rule it applies — staleness, clock skew, duplicate window, rate limit — has to be judged in capture time. Judge them at delivery and a 10-minute outage drains as stale, duplicate and rate-limited all at once. The design, the clocks, and the audit that caught it before production.
Every Business Outcome Is a 200: Designing a Webhook Whose Response Is a Meal
A biometric terminal at a canteen POSTs a scan; the response decides whether the person in front of it gets lunch. The design decisions behind that endpoint: why 'no plan' is a 200 and never a 500, HMAC over raw bytes before JSON parsing, claiming the idempotency key before validating anything, anchoring the meal to scan time rather than arrival time, outlet-scoped identity, collapsing six round trips into one gather-only RPC, and recording a fact the tables could not reconstruct afterwards.
Do Small Tables Need Indexes? How a 114-Row Table Read 166 Billion Tuples
pg_stat_user_tables said a 114-row lookup table had been sequentially scanned 1.6 billion times. Each scan was trivially cheap; the repetition was not. Where per-row scans of tiny tables come from — correlated subqueries in PL/pgSQL, per-row resource embeds — how to find them, why adding an index is only half the fix, and what CREATE INDEX CONCURRENTLY does and doesn't promise.
Razorpay Won't Move 81 Paise: Paying Vendors Exactly Once When the Customer's Share Is Tiny
A 99%-subsidised ₹100 order collects ₹1 from the customer. The vendor's slice of that ₹1 is 81 paise — below the gateway's minimum transfer — so checkout failed for the people with the best subsidy. Why splitting the customer's payment is the wrong model once an employer pre-funds the meal, how to pay the vendor the full net from your own balance instead, and how to make 'pay once' a property of the row when a webhook and a cron can both pay.
Timeouts That Don't Cancel: Why a Client Abort With No Server Timeout Is a Load Multiplier
An 8-second client-side abort on every database call felt like a safety net. With no statement_timeout on Postgres it was the opposite: every timed-out query kept running while the client retried on top of it, so under saturation load could only add, never shed. A postmortem of a Postgres death spiral — the chronic base load nobody read, the bulk onboarding that lit it, the retry loops that fed it, and the one server-side setting that breaks the cycle.
The Audit Trail Went Silent for Four Days and Nothing Alerted
A stored procedure started failing on every call with Postgres error 42702, and orders kept succeeding — because the caller logged the failure and continued. How an output parameter shadows a column in PL/pgSQL, why a guard on the happy path fails everything at once, and how to alert on writes that stopped happening.
You Can't Cover a 2.36% Fee by Adding 2.36%: Gross-Up Arithmetic for Payment Fees
A payment gateway takes its cut from what it processes, not from what you keep — so adding the fee rate back always lands short. The gross-up formula, the rounding traps that make the parts stop summing, and why the credited amount must never come from the client.
GitHub Just Shipped Stacked Pull Requests. I've Been Faking Them for Years.
GitHub's native stacked pull requests hit public preview on July 30, 2026 — dependent PRs that review as separate layers and rebase server-side when the bottom one merges. I've hand-rolled this workflow with 'depends on #142' comments and rebase cascades for years. Here's how the native version works, what it replaces, and where I'd still be careful.
Same Row, Three Truths: A Three-Party Workflow in Postgres
Client, operator, caterer — one record, three audiences, none trusting the others. Modelling a three-party workflow in Postgres with read-time projections.
Store Grams, Not Bags: Designing a Multi-Unit Inventory Ledger in Postgres
A kitchen buys in bags, cooks in grams, counts in kilos. How I modelled a multi-unit inventory ledger in PostgreSQL: one base unit, one lock target, one log.
I Automated 90% of a Grungy Data-Entry Job Without an LLM — and Where an Agent Would Actually Earn Its Place
The hype says throw an AI agent at manual data entry. I automated a menu-onboarding pipeline — hundreds of items, images, matching — with boring deterministic code and a human-in-the-loop for the ambiguous 10%. Here's the pipeline, why an LLM would have been the wrong first tool, and the exact two places a vision-language model does earn its cost.
Fine-Grained RBAC for a Multi-Tenant SaaS — Without a Policy Engine
You don't need OPA, Zanzibar, or a rules DSL to do least-privilege access control in a multi-tenant SaaS. A permission set, a role→permission map, and one tenant-scoped middleware get you fine-grained, auditable authorization in plain SQL. Here's the model, the two checks every request needs, and the mistakes that leak data across tenants.
I Have Idempotent Search Endpoints Doing POST. Should They Be QUERY?
HTTP finally got a safe, idempotent, cacheable method that carries a request body — QUERY, standardized as RFC 10008 in June 2026. I have real filter endpoints that abuse POST for exactly this reason. Here's what QUERY actually fixes, what breaks if you adopt it today, and my honest verdict on whether to migrate.
Never Trust the Client to Create the Order: Making Payment the Source of Truth
A restaurant order that exists only after the customer's app calls your API is one crashed screen away from a paid-but-missing order. Here's how I moved order creation behind the payment webhook — and the three layers of idempotency it took to survive the race that created.
You Probably Don't Need Kafka: A Durable Outbox and Job Queue in Plain Postgres
Before you add Kafka to send an email after an order, look at the database you already run. A transactional outbox plus FOR UPDATE SKIP LOCKED gives you a durable, at-least-once job queue with exactly-once side effects — no broker, no second system to operate. Here's the whole pattern, and the four things that bite you in production.
Everything I Got Wrong Building Real-Time Order Tracking with Socket.IO
Live order tracking looks trivial in the demo: emit an event, the map moves. Then production teaches you that a WebSocket is a lossy notification channel, not a database. Nine mistakes I made building real-time tracking on Socket.IO — disconnects, missed events, rooms, scaling across instances — and the one rule that fixes most of them.
Reconciliation, Not Sync: Integrating a System That Also Webhooks You Back
You can't 'sync' two systems that both change the data and both notify each other — you get echo loops, out-of-order events, and fields that silently disagree. The honest model is reconciliation: pick an owner for every field, apply inbound events idempotently, and run a periodic sweep that repairs the delta. Here's the pattern, from a real bidirectional POS integration.
15 Production Mistakes I Made Scaling a Node.js SaaS to 20,000 Users
An honest retrospective of 15 backend mistakes made while scaling a multi-tenant food-tech SaaS to 20,000+ users and 50,000 meals a month - from query assumptions and missing pagination to concurrency bugs, file storage, and optimizing too late.
Scaling PostgreSQL in Production: Indexing, RPC Functions, Views, and Query Optimization
A practical look at how composite indexes, database views, Supabase RPC functions, pagination, and query optimization techniques improved the performance and maintainability of a multi-tenant PostgreSQL system handling over 120,000 daily database requests.
Building an Atomic Delivery Dispatcher with PostgreSQL Row-Level Locking
How to design a high-reliability delivery batching and dispatch engine in TypeScript. Learn how to prevent rider capacity overload using PostgreSQL row-level locks, enforce strict order states, and store delivery tracking proofs using JSONB.
Designing a Multi-Tenant Subsidy Engine with Concurrency-Safe Budget Tracking
How to architect a B2B meal allowance and subsidy calculation system in Node.js and PostgreSQL. We walk through schema mapping, resolving discount conflicts using the Maximum Rule, and preventing budget depletion race conditions.
How I Cut AWS Costs by 60% by Fixing Infrastructure Inefficiencies
How to cut an AWS bill by 60% without re-architecting: trim verbose request logging that floods CloudWatch and S3, add S3 lifecycle rules so logs and uploads expire, and move static asset serving off Node.js onto NGINX. A line-by-line breakdown of $1,250/month down to $480.
Scaling a Food-Tech SaaS from 1K to 20K Users
What actually broke scaling an institutional food-services platform from 1,000 to 20,000+ users: queries that were fine at 10,000 rows and timed out at 200,000, connection churn, and an 80%-of-orders-in-90-minutes lunch rush. The fixes were composite indexes, pagination, pre-computed quotas and a background queue.
Node.js Performance Optimization: From Slow APIs to Scalable Systems
Five Node.js performance fixes measured with Autocannon on one t3.medium: keeping CPU work off the event loop, PM2 cluster mode across cores, Redis caching for read-heavy routes, index-aware queries and gzip compression. Result: p95 latency 420ms to 18ms and throughput 240 to 4,200 req/sec.
Zero-Downtime Deployments with PM2 and NGINX
How to deploy a Node.js API with zero downtime using PM2 cluster mode and NGINX: rolling reloads instead of restarts, a SIGINT handler that drains in-flight requests, proxy_next_upstream retries, and a webhook-driven CI/CD pipeline. From a production platform that went from 30-second deploy outages to none.