← Back to portfolio

System Design

Amazon / Flipkart — High-Level Design

A walkthrough of how I'd design an e-commerce platform like Amazon, Flipkart, or Meesho — from a monolith to microservices, a CDC-backed search index, S3/CDN images, Redis locks on the last unit, and Kafka after payment. Below is the design, step by step.

System DesignCDCElasticsearchRedisKafkaMicroservices
  1. 01

    Requirements & API

    Search, view a product, add to cart, checkout, pay, and track an order. Scale target is 10M MAU and ~10 orders/sec. Orders and payments need strong consistency; search and product views need high availability. Core entities are User, Product, Inventory, Order, and Payment — with APIs for search, product details, cart, checkout, payment, and order status.

    Requirements & API
  2. 02

    Day 0 — Monolith

    Every module lives on one server talking to one database: User, Search, Product, Cart, Checkout, Orders, Payments. Easy to ship. Tightly coupled, so you can only scale it vertically — and only for so long. That is the prompt to split into microservices.

    Day 0 — Monolith
  3. 03

    Day N — Microservices

    An API Gateway routes to User, Search, Product, Cart, Orders, Checkout, and Payments. Each owns its database: User DB, Product DB (shared with Search at this stage), Cart DB, Orders DB, Payments DB. Search still reading the product table will not stay cheap — that is the next problem.

    Day N — Microservices
  4. 04

    Why Search Cannot Be SQL LIKE

    GET /product/search returns a paginated product list. SELECT * FROM products WHERE name LIKE '%term%' does a full table scan. Postgres pg_trgm (trigram indexes) helps fuzzy substring search, but a dedicated engine — Elasticsearch or AWS OpenSearch — is what search is actually for.

    Why Search Cannot Be SQL LIKE
  5. 05

    Indexing Products — Dual Write vs CDC

    Search reads Elasticsearch; Products writes Postgres. Two ways to keep the index fresh: the admin write path dual-writes to both stores (fragile, easy to drift), or Change Data Capture streams product-table changes into Elasticsearch. CDC is the one that scales.

    Indexing Products — Dual Write vs CDC
  6. 06

    CDC Pipeline

    Every datastore already logs mutations: Postgres WAL, MySQL binlog, MongoDB change streams, DynamoDB Streams, Oracle redo. Debezium reads those logs, publishes to Kafka, and a consumer indexes into Elasticsearch. Product writes stay in the Product DB; Search never queries it.

    CDC Pipeline
  7. 07

    Images — S3 + CDN

    Product images live in S3; the Product DB stores the URLs (product_id, name, description, qty, images[]). A CDN like CloudFront sits in front so listing and detail pages do not pull bytes from origin. Search still follows CDC from Product DB → Elasticsearch.

    Images — S3 + CDN
  8. 08

    Product Details & Cart

    GET /products/{id} hits the Product DB (images via S3 URL + CDN). POST /cart/add {productId, quantity} writes user_id, cart_id, product_id, qty into Postgres. The Cart service calls Product internally for the latest price so a stale cart cannot checkout at yesterday's number.

    Product Details & Cart
  9. 09

    Checkout — Inventory Check

    Checkout asks Inventory (source of truth, indexed on product_id) if the SKU is in stock. If yes, Payments talks to Stripe / Razorpay and records success and failure in Payments DB. Inventory is not the Product catalog — it is the stock ledger checkout actually trusts.

    Checkout — Inventory Check
  10. 10

    Payment Confirmed → Kafka

    User clicks checkout → Inventory says yes → pay. On payment-done, publish a Kafka topic. Three consumers fan out: Order consumer inserts into Orders DB, Inventory consumer decrements qty, Notification service sends email and SMS. The request path stays short; fulfillment is async.

    Payment Confirmed → Kafka
  11. 11

    End-to-End Checkout

    Gateway → Checkout → Inventory yes/no → Payments → Stripe/Razorpay → payment-done → Kafka. Order, inventory, and notification consumers run in parallel. Search, Product, Cart, and User sit beside this path with their own stores; CDC keeps Elasticsearch honest.

    End-to-End Checkout
  12. 12

    Redis Lock — Last iPhone

    One iPhone left, four users at checkout. A Redis distributed lock makes the inventory check + payment critical section run for one requester at a time. Without it, all four can read qty=1 and all four can pay. The lock is the difference between overselling and a single winner.

    Redis Lock — Last iPhone
  13. 13

    Inventory Cache

    Inventory DB is the source of truth; a cache in front keeps availability reads from knocking it over. On write, invalidate or update the cached qty so the next check is not serving a ghost unit. Cache is a shield, not a second ledger.

    Inventory Cache
  14. 14

    Product Qty vs Inventory

    After payment, Inventory DB decrements. The Product DB still shows quantity on the PDP, so CDC from Inventory → Product keeps that number in sync. Catalog qty is eventually consistent; the lock + Inventory DB are what prevent the double sell.

    Product Qty vs Inventory
  15. 15

    Putting It All Together

    User hits CDN for images and the API Gateway for everything else. Search reads Elasticsearch (CDC from Product). Cart, Orders, User, Payments each own a DB. Checkout takes a Redis lock, asks Inventory (cached), charges via Stripe/Razorpay, then Kafka fans out order creation, stock decrement, and notifications.

    Putting It All Together
← Back to portfolio