Skip to content
Design Amazon: E-Commerce Platform Architecture

Design Amazon: E-Commerce Platform Architecture

DodaTech Updated Jun 20, 2026 8 min read

Designing an e-commerce platform like Amazon means building a system that handles millions of products, billions of orders, real-time inventory, secure payments, personalized recommendations, and global search — all while maintaining 99.99% availability during peak events like Black Friday.

What You’ll Learn

You’ll master product catalog denormalization for search, shopping cart persistence with Redis, order processing workflows, 2-phase commit vs saga patterns for payments, inventory management, recommendation engines, and Elasticsearch-powered product search.

Why This Problem Matters

Amazon processes over 1.6 million orders per day globally. During Prime Day, it handles 100,000+ orders per minute. E-commerce architecture combines virtually every system design concept: caching, databases, search, payments, inventory, and ML. At DodaTech, e-commerce patterns secure transactions in Doda Browser’s payment flows and power Durga Antivirus Pro’s license management.

System Design Learning Path

    flowchart LR
  A[Ride Sharing] --> B[Social Media Feed]
  B --> C[E-Commerce Platform]
  C --> D{You Are Here}
  style D fill:#f90,color:#fff
  

Architecture Overview

    flowchart TB
    User[User] --> LB[Load Balancer]
    LB --> Web[Web Servers]
    Web --> Catalog[Product Catalog]
    Web --> Cart[Shopping Cart - Redis]
    Web --> Search[Search Service - ES]
    Web --> Order[Order Service]
    Web --> Recs[Recommendation Engine]
    Web --> Payment[Payment Service]
    Cart --> Redis[(Redis)]
    Catalog --> PG[(PostgreSQL - Read Replicas)]
    Catalog --> CatalogCache[(Redis Cache)]
    Order --> OrderDB[(Order DB)]
    Order --> Queue[Order Queue - SQS]
    Queue --> Workers[Order Workers]
    Workers --> Inv[Inventory Service]
    Workers --> Ship[Shipping Service]
    Workers --> Notif[Notification Service]
    Search --> ES[(Elasticsearch Cluster)]
    Payment --> PaymentGW[Payment Gateway - Stripe]
  

Product Catalog (Denormalized for Search)

An e-commerce catalog must support fast filtering, faceted search, and personalized ranking. Denormalization is key:

{
  "product_id": "B08N5WRWNW",
  "title": "Wireless Bluetooth Headphones",
  "brand": "AudioTech",
  "category": "Electronics > Audio > Headphones",
  "price": 79.99,
  "currency": "USD",
  "attributes": {
    "color": ["Black", "White", "Blue"],
    "connectivity": "Bluetooth 5.0",
    "battery_life": "30 hours",
    "weight_grams": 250
  },
  "inventory": 1500,
  "rating": 4.5,
  "review_count": 2847,
  "image_urls": ["https://cdn.example.com/img1.jpg"],
  "created_at": "2026-01-15T10:00:00Z"
}
# Product denormalization service
import json, random

class ProductCatalog:
    def __init__(self):
        self.products = {}

    def add_product(self, product: dict):
        pid = product["product_id"]
        # Denormalize: flatten nested attributes for search
        product["search_attrs"] = " ".join([
            product["title"].lower(),
            product["brand"].lower(),
            product["category"].lower(),
            *[v.lower() if isinstance(v, str) else str(v) for v in product["attributes"].values()]
        ])
        self.products[pid] = product

    def search(self, query: str, category: str = None, max_price: float = None) -> list:
        results = []
        query = query.lower()
        for pid, p in self.products.items():
            if query not in p["search_attrs"]:
                continue
            if category and category.lower() not in p["category"].lower():
                continue
            if max_price and p["price"] > max_price:
                continue
            results.append(p)
        return sorted(results, key=lambda x: x["rating"], reverse=True)[:20]

catalog = ProductCatalog()
catalog.add_product({
    "product_id": "H001", "title": "Sony WH-1000XM5", "brand": "Sony",
    "category": "Electronics > Audio > Headphones", "price": 349.99,
    "attributes": {"color": "Black", "connectivity": "Bluetooth 5.2", "battery_life": "40h"},
    "rating": 4.7, "review_count": 15000
})
catalog.add_product({
    "product_id": "H002", "title": "Bose QC45", "brand": "Bose",
    "category": "Electronics > Audio > Headphones", "price": 329.99,
    "attributes": {"color": "Silver", "connectivity": "Bluetooth 5.1", "battery_life": "35h"},
    "rating": 4.5, "review_count": 12000
})
results = catalog.search("bluetooth headphones", max_price=400)
print(f"Found {len(results)} products:")
for p in results:
    print(f"  {p['title']} — ${p['price']}{p['rating']}★")

Output:

Found 2 products:
  Sony WH-1000XM5 — $349.99 — 4.7★
  Bose QC45 — $329.99 — 4.5★

Shopping Cart Persistence (Redis)

Shopping carts must survive server restarts, across devices, without being lost:

AspectGuest CartLogged-In Cart
Keycart:session:{session_id}cart:user:{user_id}
TTL30 daysIndefinite
Merge on loginYesYes
StorageRedis HashRedis Hash + Postgres backup
import json, time

class ShoppingCart:
    def __init__(self, redis_client=None):
        self.redis = redis_client or {}
        self.carts = {}

    def add_item(self, user_id: str, product_id: str, quantity: int = 1):
        if user_id not in self.carts:
            self.carts[user_id] = {}
        if product_id in self.carts[user_id]:
            self.carts[user_id][product_id]["quantity"] += quantity
        else:
            self.carts[user_id][product_id] = {
                "quantity": quantity,
                "added_at": time.time()
            }
        return self.carts[user_id]

    def get_cart(self, user_id: str) -> dict:
        return self.carts.get(user_id, {})

    def merge_carts(self, guest_id: str, user_id: str):
        guest_cart = self.carts.pop(guest_id, {})
        if user_id not in self.carts:
            self.carts[user_id] = {}
        for pid, item in guest_cart.items():
            if pid in self.carts[user_id]:
                self.carts[user_id][pid]["quantity"] += item["quantity"]
            else:
                self.carts[user_id][pid] = item

cart = ShoppingCart()
cart.add_item("guest-abc", "H001", 1)
cart.add_item("user-42", "H002", 2)
cart.merge_carts("guest-abc", "user-42")
print(f"Cart for user-42: {cart.get_cart('user-42')}")

Order Processing Workflow

    flowchart LR
    A[Checkout] --> B[Validate Cart]
    B --> C[Reserve Inventory]
    C --> D[Process Payment]
    D -->|Success| E[Confirm Order]
    D -->|Failure| F[Release Inventory]
    E --> G[Send to Warehouse]
    G --> H[Assign Carrier]
    H --> I[Ship]
    F --> J[Notify User - Payment Failed]
    E --> K[Send Confirmation Email]
  

Saga Pattern for Payments

Using 2-phase commit across microservices is impractical (blocks resources). The saga pattern coordinates distributed transactions with compensating actions:

class OrderSaga:
    def __init__(self):
        self.steps = []

    def execute(self, order: dict) -> bool:
        try:
            if not self._reserve_inventory(order):
                raise Exception("Inventory reservation failed")
            self.steps.append("inventory_reserved")

            if not self._process_payment(order):
                raise Exception("Payment failed")
            self.steps.append("payment_processed")

            if not self._confirm_order(order):
                raise Exception("Order confirmation failed")
            self.steps.append("order_confirmed")

            return True
        except Exception as e:
            print(f"Order failed at step '{self.steps[-1] if self.steps else 'start'}': {e}")
            self._compensate()
            return False

    def _reserve_inventory(self, order: dict) -> bool:
        print(f"Reserving inventory for {order['items']}")
        return True

    def _process_payment(self, order: dict) -> bool:
        print(f"Processing payment of ${order['total']}")
        # Simulate payment failure
        return order.get("valid_payment", True)

    def _confirm_order(self, order: dict) -> bool:
        print("Confirming order")
        return True

    def _compensate(self):
        for step in reversed(self.steps):
            if step == "inventory_reserved":
                print("Compensating: releasing inventory")
            elif step == "payment_processed":
                print("Compensating: issuing refund")
            elif step == "order_confirmed":
                print("Compensating: cancelling order")

saga = OrderSaga()
result = saga.execute({"items": ["H001"], "total": 349.99, "valid_payment": False})
print(f"Order result: {'Success' if result else 'Failed'}")

Output:

Reserving inventory for ['H001']
Processing payment of $349.99
Order failed at step 'payment_processed': Payment failed
Compensating: releasing inventory
Order result: Failed

Inventory Management

Inventory must be accurate within seconds to prevent overselling:

EventInventory ChangeNotes
Item added to cartDecrement reserved countTimed release after 30 min
Order placedDecrement available countConfirmed deduction
Payment failureIncrement available countCompensating transaction
Item deliveredNo changeInventory already deducted

Recommendation Engine

Three approaches combined:

class RecommendationEngine:
    def __init__(self):
        self.purchase_history = {}
        self.product_similarity = {}
        self.trending = []

    def recommend_for_user(self, user_id: str, limit: int = 5) -> list:
        # 1. Collaborative: users who bought X also bought Y
        collab = self._collaborative_filtering(user_id)

        # 2. Content-based: similar products to past purchases
        content_based = self._content_based(user_id)

        # 3. Trending: popular products right now
        trending = self._get_trending()

        # Merge with weights
        seen = set()
        results = []
        for item in collab + content_based + trending:
            if item not in seen:
                results.append(item)
                seen.add(item)
        return results[:limit]

    def _collaborative_filtering(self, user_id: str) -> list:
        return ["H002", "H003"]  # Example

    def _content_based(self, user_id: str) -> list:
        return ["H005", "H008"]

    def _get_trending(self) -> list:
        return ["H010", "H012"]

recs = RecommendationEngine()
print(f"Recommended: {recs.recommend_for_user('user-42')}")

Search with Elasticsearch

Product search uses Elasticsearch with specialized analyzers:

  • Autocomplete: Edge n-gram tokenizer for prefix matching
  • Typo tolerance: Fuzzy matching (Levenshtein distance 2)
  • Faceted search: Aggregations on category, brand, price range, rating
  • Personalized ranking: Boost products based on user history
{
  "query": {
    "bool": {
      "must": { "match": { "title": "wireless headphones" } },
      "filter": [
        { "term": { "category": "Electronics > Audio" } },
        { "range": { "price": { "lte": 500 } } }
      ]
    }
  },
  "sort": [
    { "_score": "desc" },
    { "rating": "desc" }
  ]
}

Common Errors

  1. Stale inventory counts: Using eventual consistency for inventory causes overselling. Always use strong consistency (Redis atomic counters or PostgreSQL serializable isolation) for inventory.

  2. No cart persistence: Losing a user’s cart after a server restart destroys conversion rates. Persist cart state in Redis with periodic backups to SQL.

  3. Synchronous payment processing: Blocking the order thread during payment (2-5 seconds) wastes server resources. Use async payment processing with webhook callbacks.

  4. Missing idempotency keys: Processing the same order twice due to network retries. Each checkout request should include an idempotency key that the server deduplicates.

  5. No black Friday capacity planning: E-commerce sites that handle 1000 req/s normally must handle 100x spikes on Black Friday. Auto-scale aggressively and use queue-based load shedding.

Practice Questions

1. How does Amazon handle Black Friday traffic spikes?
Amazon auto-scales its web tier aggressively, uses queue-based load shedding for order processing, and pre-warms Elasticsearch and cache clusters. The catalog tier is read-heavy and fully cached.
2. What’s the difference between 2-phase commit and saga pattern?
2PC locks resources across services until the coordinator decides commit/abort (blocking). Saga executes compensating transactions on failure (non-blocking). Saga is preferred for microservices because it doesn’t hold locks.
3. How would you design a flash sale feature?
Pre-allocate inventory in Redis. Use optimistic locking with atomic decrement. Show “sold out” when inventory reaches zero. Queue orders asynchronously. Rate-limit requests per user to prevent bots.
4. Challenge: Design a marketplace for 10 million sellers with 500 million products.
Use sharded product catalog by category ID. Each shard is an independent Elasticsearch cluster. Search queries fan out to all shards and merge results. Seller dashboards are relegated to a separate analytics cluster.

Mini Project

Build an e-commerce order processing system:

import random, uuid, time

class ECommerceSystem:
    def __init__(self):
        self.products = {}
        self.orders = {}
        self.carts = {}

    def add_product(self, pid: str, name: str, price: float, stock: int):
        self.products[pid] = {"name": name, "price": price, "stock": stock}

    def add_to_cart(self, user: str, pid: str, qty: int):
        if user not in self.carts:
            self.carts[user] = {}
        if pid not in self.products or self.products[pid]["stock"] < qty:
            return False
        self.carts[user][pid] = self.carts[user].get(pid, 0) + qty
        return True

    def checkout(self, user: str) -> str:
        cart = self.carts.get(user, {})
        if not cart:
            return "Cart empty"
        total = 0
        order_id = str(uuid.uuid4())[:8]
        items = []
        for pid, qty in cart.items():
            product = self.products[pid]
            if product["stock"] < qty:
                return f"Insufficient stock for {product['name']}"
            product["stock"] -= qty
            total += product["price"] * qty
            items.append({"pid": pid, "qty": qty, "price": product["price"]})
        self.orders[order_id] = {"user": user, "items": items, "total": total, "status": "confirmed"}
        del self.carts[user]
        return f"Order {order_id} confirmed. Total: ${total:.2f}"

store = ECommerceSystem()
store.add_product("P1", "Laptop", 999.99, 10)
store.add_product("P2", "Mouse", 29.99, 50)
store.add_to_cart("alice", "P1", 1)
store.add_to_cart("alice", "P2", 2)
print(store.checkout("alice"))

Cross-References

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro