Blogs

Practical notes from production work: architecture decisions, database performance, reliability, and business impact.

22 notes
May 20, 20268 min read

PostHog and Upstash Redis on a Multi-Role Next.js App

Page view analytics didn't show where 40% of students were dropping out of enrollment. Custom PostHog funnel events did. Upstash Redis handled edge rate limiting that standard Redis couldn't — no persistent TCP connection needed. What each tool actually delivered.

PostHogUpstash RedisNext.jsAnalyticsRate Limiting
Read more
May 18, 20267 min read

Layered Security in Next.js Middleware Before Auth Runs

Bot probes for /wp-admin and /.env were hitting Supabase auth on every request — 400 unnecessary API calls in 2 minutes from a single scanner. Ordering middleware checks by cost (regex first, Supabase last) cut 60% of those calls before they reached auth.

Next.jsSecurityMiddlewareRate LimitingSupabase
Read more
May 16, 20268 min read

Building RBAC for Six User Roles in One Next.js App

If-else chains in page components break at four roles and enforce nothing at the API layer. Two enforcement points — middleware prefix guards and per-server-action requireAuth checks — plus a critical Supabase gotcha: reading role from user_metadata lets users escalate their own privileges.

RBACNext.jsSupabaseSecurityTypeScript
Read more
Apr 18, 20265 min read

MySQL2 Connection Pooling: What Happens Without It at Scale

Per-request connections worked fine in development. In production with 32 PM2 workers across 8 brands, connection overhead became the bottleneck — not CPU or memory. The createPool settings that eliminated connection errors under peak concurrent load.

MySQL2Connection PoolingNode.jsPerformanceDatabase
Read more
Apr 15, 20266 min read

Fixing N+1 Queries in a Live Multi-Store System

The morning report fetched a list of 100 stores, then ran a separate aggregation query for each one — 101 round trips per page load, every day. A single JOIN with GROUP BY replaced all 101 queries and dropped load time from 3+ seconds to under 40ms.

N+1 Query ProblemMySQLSQL OptimizationPerformanceNode.js
Read more
Apr 12, 20267 min read

Bulk INSERT and UPDATE in Node.js Without an ORM

20,000 individual INSERT/UPDATE queries meant 20,000 round trips — at 2ms each, that's 40 seconds of network overhead before any query work. Multi-row VALUES INSERT and CASE WHEN UPDATE collapsed 20K queries to 14 and cut upload time to 8 seconds.

Node.jsMySQLSQLBulk OperationsPerformance
Read more
Apr 10, 20266 min read

How I Chose Composite Indexes on a 500K-Record System

Adding an index on every column creates write overhead without solving anything. On a 500K-row system, EXPLAIN showed full table scans on multi-column WHERE clauses — individual column indexes wouldn't have helped. Here's how to read the query patterns and place composite indexes correctly.

MySQLComposite IndexesQuery OptimizationPerformanceDatabase
Read more
Apr 8, 20268 min read

Building a 5-Phase CSV Upload Pipeline That Handles 30K Rows

A single-pass CSV loader failed in three ways at 30K rows: duplicates, partial commits on crashes, and 120K extra queries from inline inventory sync. A 5-phase pipeline inside one transaction fixed all three and now processes 30K rows in 8 seconds.

Node.jsCSV ProcessingMySQLBackendData Pipeline
Read more
Apr 5, 20265 min read

Replacing Array.find() with Map for O(1) Lookups in Data Pipelines

Array.find() inside a loop that runs 20K times means up to 400 million comparisons — O(n²). The insert queries were fast; the JavaScript between them was the bottleneck. Switching to JavaScript Map for O(1) lookups made the CPU cost negligible.

JavaScriptPerformanceData StructuresNode.jsOptimization
Read more
Apr 2, 20265 min read

Keeping TypeScript Type Safety When You Drop the ORM

Switching from Prisma to raw MySQL2 means your query results become RowDataPacket[] — effectively any[]. Hand-writing interfaces creates two sources of truth that will drift. Using Prisma-generated types as casts on MySQL2 results gives you full type safety from a single schema source.

TypeScriptMySQL2PrismaDeveloper ExperienceNode.js
Read more
Mar 10, 20268 min read

From 2-Minute Reports to 30-Second Queries in Production

Morning MOS reports took 2 minutes because of a full table scan, 101 queries per page load, and a JOIN column type mismatch. Three targeted fixes — composite indexes, a single GROUP BY query, and a type normalization — cut it to 30–40 seconds.

MySQLQuery OptimizationPostgreSQLPerformanceSQL
Read more
Jan 10, 202614 min read

One Codebase, 8 Brands, 100+ Stores: How Configuration Replaced Forking

Eight forks would have meant eight bug fixes and eight deployments every time. A JSON config file, PM2 cluster mode, and a MySQL2 migration instead turned a 2-week-per-brand onboarding into under 5 minutes — while cutting memory per upload from 2 GB to 128 MB.

Multi-tenant ArchitectureNode.jsPM2MySQL2PostgreSQL
Read more
Nov 10, 20256 min read

PM2 Clustering in Practice: 4x Throughput on the Same Hardware

A Next.js app running as a single process was using 25% of a 4-core server. Switching to PM2 cluster mode with 4 workers per brand pushed throughput from ~100 to ~800 req/sec and uptime from 95% to 99.95% — without buying new hardware.

PM2Node.jsPerformanceCluster ModeInfrastructure
Read more
Sep 10, 20257 min read

Batch Processing That Cut Memory Use by 16x

Loading 30K CSV rows in one pass consumed over 2 GB per upload — two concurrent uploads crashed the server. Processing in chunks of 3,000 with per-batch transactions dropped memory to 128 MB and brought out-of-memory errors to zero.

Node.jsBatch ProcessingMySQLPerformanceBackend
Read more
Jul 10, 20257 min read

Automating Stock Reconciliation Across 4 Tables

Manual stock reconciliation across 4 inventory tables took 2 days per week — 104 hours a month of cross-referencing spreadsheets. A single ACID transaction that updates all 4 tables in order cut that to under 1 hour daily with a full audit trail.

MySQLInventory ManagementSQLACID TransactionsNode.js
Read more
May 10, 20256 min read

React Query Caching Strategy for Busy Business Dashboards

Every tab switch in the dashboard triggered a fresh API call — same data, same response, fetched again. A 5-minute staleTime, 30-minute cacheTime, and disabled refetchOnWindowFocus cut redundant API requests by 70% and made repeat views instant.

React QueryNext.jsPerformanceFrontendCaching
Read more
Mar 10, 20256 min read

What On-Premise Deployment Taught Me About Real Constraints

Cloud deployment hides process management, restarts, and connection handling behind platform defaults. Deploying on a client's 4-core server with no managed services made every one of those assumptions explicit — and forced better infrastructure decisions upfront.

On-Premise DeploymentPM2Node.jsInfrastructureDevOps
Read more
Jan 10, 20257 min read

Designing One Dashboard for Executives, Managers, and Warehouse Teams

Five operational roles, one codebase — without turning every page into a wall of conditionals. Module-level visibility gates and action-level permission checks kept the interface useful for each team without exposing what shouldn't be seen.

RBACProduct DesignNode.jsOperationsArchitecture
Read more
Nov 10, 20246 min read

How to Build Business Systems That Actually Save Time

Internal tools fail because developers build what they think the workflow should be, not what it is. Watching the team work first, targeting the highest manual cost, and designing for exception cases upfront is what keeps systems in use after month three.

Business SystemsSoftware DeliveryOperationsProduct Engineering
Read more

New production notes added regularly.