Mahbubul Alam.
Architecture

From Zero to Production: Architecting an E-commerce Platform for Millions of Users

Jul 20263 min read
From Zero to Production: Architecting an E-commerce Platform for Millions of Users

Start with the write path, not the read path

Most architecture discussions obsess over read scaling — CDNs, caching, read replicas — because it's the more visible traffic. But the part of an e-commerce platform that actually breaks under load first is the write path: checkout, inventory decrements, payment confirmation. Reads are embarrassingly parallel and cache beautifully; writes have to be correct under concurrency, and correctness doesn't parallelize for free. I design the write path first — the order state machine, the payment idempotency strategy, the inventory reservation logic — and treat the read path as an optimization problem to solve once the write path is provably correct.

The schema split that paid off

Product catalog, cart contents, and content (banners, category pages) went into MongoDB — the data is naturally document-shaped, changes shape often as the catalog grows new attribute types, and doesn't need multi-row transactions. Orders, payments, and inventory ledgers went into PostgreSQL — this is money-adjacent data with real relational integrity requirements, and I wanted foreign keys and transactions doing the work of preventing a half-committed order rather than application code trying to simulate them.

Caching that actually reduced load

A Redis cache-aside layer in front of product reads cut database load by roughly three-quarters on high-traffic endpoints. The detail that mattered more than the caching itself was cache invalidation granularity — caching an entire product list by category and invalidating the whole key on any single product update thrashes the cache constantly during a busy admin session. Keying the cache per-product and composing list views from individual cached entries meant a single product edit invalidated one key, not thousands.

Payments: idempotency before features

Every payment gateway integration (card, bKash, SSLCommerz) got an idempotency key generated client-side at "place order" time, before any network call. If the client retries — a flaky connection, a double-tap on the pay button — the same key hits the same in-flight or completed transaction instead of creating a second charge. This is the single highest-leverage piece of payment infrastructure I've built: it's invisible when it works, and it's the difference between a support ticket and a real financial reconciliation problem when it doesn't.

Real-time order tracking

Socket.io pushed order status changes to the buyer's session the instant they happened, replacing what would otherwise be a polling loop hitting the API every few seconds per active user. At scale, that polling load is significant and mostly wasted — most polls return "nothing changed." A push model uses a fraction of the request volume for the same user experience, at the cost of needing sticky sessions or a shared adapter (Redis pub/sub) once you run more than one server instance.

What I'd do differently

I underestimated how much operational tooling — the admin dashboard, inventory alerts, RBAC — would matter to the actual business outcome. The customer-facing storefront is the part everyone designs first; the internal tools that let a non-engineer run the business day to day are what determine whether the platform is actually usable once it's live. I'd build the admin surface earlier next time, in parallel with the storefront, not after it.

#e-commerce#scalability#architecture