Design Amazon: E-Commerce Platform Architecture
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:
| Aspect | Guest Cart | Logged-In Cart |
|---|---|---|
| Key | cart:session:{session_id} | cart:user:{user_id} |
| TTL | 30 days | Indefinite |
| Merge on login | Yes | Yes |
| Storage | Redis Hash | Redis 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: FailedInventory Management
Inventory must be accurate within seconds to prevent overselling:
| Event | Inventory Change | Notes |
|---|---|---|
| Item added to cart | Decrement reserved count | Timed release after 30 min |
| Order placed | Decrement available count | Confirmed deduction |
| Payment failure | Increment available count | Compensating transaction |
| Item delivered | No change | Inventory 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
Stale inventory counts: Using eventual consistency for inventory causes overselling. Always use strong consistency (Redis atomic counters or PostgreSQL serializable isolation) for inventory.
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.
Synchronous payment processing: Blocking the order thread during payment (2-5 seconds) wastes server resources. Use async payment processing with webhook callbacks.
Missing idempotency keys: Processing the same order twice due to network retries. Each checkout request should include an idempotency key that the server deduplicates.
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.
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