URL Shortening Service like TinyURL
Scalable system design for creating short URLs, redirecting users, and collecting analytics.
Cloud-native e-commerce platform on AWS
Denser dots = higher throughput. Hover any edge for its full contract.
Rough, order-of-magnitude numbers. The point is to justify the architecture, not to be exact.
No system is born with queues and caches. Every box was added because something was about to break. Play a stage and the diagram above shrinks to what existed at that point in the story.
Every request already passes through DNS → CloudFront → WAF → ALB before reaching the API Gateway — even a new storefront assumes this baseline network posture. From there the gateway routes to whichever business service owns the request: User, Product, or Order. Order Service calls Payment Service directly to charge the card and gets a synchronous answer back, which is what lets checkout resolve immediately instead of guessing.
This is a real, working store — but every read, popular or not, round-trips to Postgres, and there's no way to search the catalog beyond exact lookups.
Scale pressure: Session lookups and order-status checks were hammering Postgres on every request, and profile reads competed with registration/profile-edit writes on the same primary.
A Redis Cluster goes in front of User, Product, and Order Service for their hottest reads, and User DB gets a read replica dedicated to profile lookups. Neither change touches correctness — a cache miss or a replica-lag read both still fall back to the authoritative database.
Browsing is fast again, but the catalog still can't do full-text search, and product images are proxied through the app tier instead of a dedicated store.
Scale pressure: 50M SKUs of free-text, typo-tolerant search was straining Postgres LIKE queries, and CloudFront was fetching product images through Product Service instead of a dedicated object store.
Elasticsearch joins as a read-only, rebuildable index of Product DB, built for ranking and full-text queries — never the source of truth for price or stock. Product images move to S3, with CloudFront caching them at the edge and falling back to S3 on a miss.
Browsing and search are both fast now, but every order confirmation email still blocks on Notification logic living inline, and nothing decouples order-state changes from downstream consumers.
Scale pressure: Order and payment state changes had no way to reach downstream consumers without those consumers slowing down checkout itself.
Order Service and Payment Service now publish Order Events and Payment Events onto an Event Bus (Kafka) without waiting for a response, and a standalone Notification Service consumes them to send confirmation, failure, and shipping emails through SendGrid.
This is the complete system: the checkout request only ever waits on the hops that must block, and everything else — search, email, analytics-adjacent work — happens off to the side.
A diagram shows what exists; a trace shows what happens. Press play on a flow, or click any step, and the diagram above lights up the exact path that request takes.
From a client request to ranked search results and rendered product images.
From tapping "Place Order" to a confirmed, paid order and a confirmation email.
The failure path when the card issuer says no.
Every box on the canvas, and the job it does. Click a card to locate it in the diagram; click the node itself for the full deep dive.
This diagram represents a cloud-native e-commerce platform built on AWS using a microservices architecture. Each business capability — users, products, orders, payments, notifications — is deployed as an independent service with its own dedicated data store. This design enables teams to develop, deploy, and scale services independently.
Traffic flows through a layered network stack before reaching the application services:
1. DNS (Route 53) resolves the domain and routes requests to CloudFront (CDN) for edge caching. 2. CloudFront forwards to WAF (Web Application Firewall) for security filtering. 3. Clean traffic reaches ALB (Application Load Balancer) which distributes across targets. 4. API Gateway receives all requests and routes them to the appropriate internal service via gRPC.
This multilayered approach provides defence in depth — each layer adds caching, security, or routing capabilities without coupling to business logic.
Order Events to the Event Bus (Kafka) whenever an order changes state. This decouples order processing from downstream consumers.Payment Events to the event bus for downstream consumers like Notification Service.All data services are grouped under a data-tier boundary:
This architecture prioritizes scalability and team autonomy over simplicity. The trade-offs are:
For most early-stage products, a modular monolith is a better starting point. Migrate to microservices when you have clear evidence that team velocity or scalability demands it. This architecture is what a mature, high-traffic e-commerce platform looks like — it's a target state, not a starting point.
An architecture is a record of trade-offs. For every major choice here: what won, what lost, and why the constraints made it so.
At a 500:1 browse-to-checkout ratio, the gateway forwards a huge volume of internal calls on every page load. gRPC's binary framing and generated stubs cut serialization overhead and catch schema drift at compile time — a REST/JSON hop would add latency exactly where the search p95 budget is tightest. GraphQL federation solves a different problem (client-driven queries), not internal service-to-service fan-out.
At 1M orders/day, the access pattern is almost entirely single-key writes and point lookups by orderId — the same access-pattern-first modeling DynamoDB's single-table design is built around. A single Postgres instance would need hand-rolled sharding to hit the same write throughput, without gaining any relational feature the order service actually uses.
Order Service calls Payment Service directly so the shopper gets a definitive success/decline before checkout resolves — waiting on an event round-trip would leave the UI guessing. The Payment Event published afterward still lets Notification Service and future consumers react without coupling to the checkout request. A full saga orchestrator earns its keep once a third service (e.g. inventory reservation) needs compensating actions; with two services, the direct call is simpler and just as correct.
50M SKUs with free-text queries, typo tolerance, and faceted filtering is exactly what Postgres tsvector starts to strain under past a few million rows. Elasticsearch is read-only and eventually consistent from Product DB's point of view — a stale index degrades relevance, never correctness, since the checkout stock check still hits Product DB directly.
Placing WAF behind the CDN but in front of the ALB filters payload-level attacks — SQL injection, catalog-scraping bots — before they reach any compute tier, while CloudFront still absorbs and caches the legitimate traffic. API Gateway auth alone only catches identity problems, not attacks against product-svc or order-svc themselves.
Profile lookups happen on nearly every authenticated request, competing with the small but latency-sensitive stream of writes (registration, profile edits) on the same primary. A read replica absorbs that read volume without adding cache-invalidation complexity for data that changes rarely — Redis stays reserved for genuinely hot, short-lived session state.
Reading a system means sensing where it cracks under 10× load. These are the pressure points of this design, and how it holds.
A promotional event can spike checkout QPS far past the ~2K/s peak baseline in minutes, and every checkout makes a synchronous call into Payment Service — a downstream processor with its own rate limits.
API Gateway rate-limits checkout requests per user/IP, Order Service treats the charge call as idempotent via a per-order key so retries are safe, and DynamoDB's hash-partitioned writes spread the order-write burst evenly instead of hammering one shard.
A single product going viral sends a disproportionate share of the 500:1 browse traffic at one SKU's cache key and search query, risking a stampede on Redis and Elasticsearch simultaneously on a cold cache.
Redis absorbs steady-state hot-key traffic, CloudFront caches category and product pages at the edge before they reach the gateway, and request coalescing at Product Service collapses concurrent cache misses into a single Elasticsearch query.
Because Order Service calls Payment Service synchronously, a slow or unavailable payment processor stalls every checkout in flight, not just the ones actively charging.
Order Service wraps the charge call in a timeout and circuit breaker; orders that time out stay pending rather than failing outright, and a reconciliation job resolves them once Payment Service recovers instead of losing the order.
A sale-driven burst of Order and Payment events can outpace Notification Service's consumption rate, growing an unbounded backlog on the Event Bus.
Publishing is fire-and-forget and off the checkout path by design — a growing backlog delays confirmation emails, never checkout itself. Notification Service scales its consumer group horizontally, and Kafka's retention window buys time to drain it.
Elasticsearch's view of stock levels can lag Product DB by seconds, and two concurrent checkouts can both see "in stock" for the last unit of a SKU.
Elasticsearch is used for search ranking only — the authoritative stock check happens as a conditional write against Product DB at charge time, so the second concurrent checkout fails the write and is told the item just sold out, rather than both succeeding.
Most of this architecture exists because any one box can die. Take a component offline and the diagram above shows the blast radius: what goes down, what degrades, and what the user feels.
The entire storefront goes dark — no requests reach any backend service, since every request funnels through this single front door.
Shoppers can still browse and add items to their cart, but checkout stalls — new orders stay stuck in pending instead of confirming.
No new orders can be created or updated at all — checkout fails outright. Browsing is unaffected since it doesn't touch this database.
Browsing and checkout both keep working, but every session lookup and product-cache read falls through to the database — page loads noticeably slow down under load.
Checkout and browsing are completely unaffected — shoppers can still create and pay for orders. Confirmation emails and any other event-driven consumer simply stop until the bus recovers.
Full-text product search stops returning results, but checkout is completely unaffected — the authoritative stock and price check never depended on the search index.
If you can answer these without scrolling back up, the architecture is yours. Try each one out loud before revealing.
Every great system starts as a sketch on a whiteboard. The ability to zoom out and see the whole picture (how services connect, where data flows, what breaks and why) is what separates engineers who build features from engineers who build systems. This diagram is more than boxes and arrows. It's a map of decisions, trade-offs, and intentional design.
Studying architectures isn't just about passing interviews. It's about training your intuition. The more systems you take apart, the better you get at sensing where a monolith will crack, where a queue belongs, or when a cache is hiding a deeper problem. You start seeing patterns instead of chaos.
So keep reading, keep tracing those edges, keep asking "why this way and not that way." The engineers who truly understand large systems are the ones who never stop being curious about how things fit together. That curiosity is the only ingredient that really matters.
Atharva Arbat@arbat_atharva