Design StreamShop — StreamHub's marketplace for creator merchandise: browse products, add to cart, checkout with payment, and track order fulfilment. Amazon-scale patterns at StreamHub's 50M buyer audience.
Requirements
Functional requirements
- Product catalog: browse, search, filter by creator/category/price.
- Shopping cart: add/remove items; persist across sessions.
- Checkout: shipping address, payment, order confirmation.
- Inventory management: track stock; prevent overselling.
- Order tracking: status updates (placed, shipped, delivered).
- Seller dashboard: list products, manage inventory, view orders.
Non-functional requirements
- Scale: 50M buyers; 500K sellers; 10M products; 1M orders/day.
- Checkout latency: Under 3 seconds end-to-end.
- Inventory accuracy: Zero overselling — strong consistency on stock decrements.
- Payment reliability: Exactly-once charge via idempotency keys.
- Availability: 99.99% for browse; 99.9% for checkout (graceful degradation).
Out of scope: Recommendation engine, auction/bidding, international customs/tax calculation.
Estimation
- 1M orders/day × 3 items avg = 3M order line items/day.
- 50M buyers × 5 cart items avg × 200 bytes = 50 GB cart storage — Redis with Postgres backup.
- Product catalog: 10M products × 2 KB metadata = 20 GB — Elasticsearch for search, Postgres for transactional.
- Order storage: 1M/day × 365 × 1 KB = 365 GB/year — partition by date, archive after 2 years.
- Payment: 1M transactions/day ≈ 12 QPS average — Stripe handles scale; focus on idempotency.
StreamShop e-commerce architecture
API design
GET /v1/products?query=hoodie&creator=sh_4420&page=1
GET /v1/products/{id}
POST /v1/cart/items: { "product_id": "p_123", "quantity": 2 }
GET /v1/cart
POST /v1/checkout:
headers: { Idempotency-Key: "uuid" }
body: { "shipping_address": {...}, "payment_method_id": "pm_abc" }
GET /v1/orders/{id}
GET /v1/sellers/me/orders?status=pending
PATCH /v1/sellers/me/products/{id}/inventory: { "quantity": 50 }
CREATE TABLE products (
id BIGINT PRIMARY KEY,
seller_id BIGINT NOT NULL,
title VARCHAR(256) NOT NULL,
price_cents INT NOT NULL,
inventory INT NOT NULL DEFAULT 0,
status VARCHAR(16) DEFAULT 'active'
);
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
buyer_id BIGINT NOT NULL,
status VARCHAR(16) NOT NULL,
total_cents INT NOT NULL,
shipping_addr JSONB,
payment_id VARCHAR(64),
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE order_items (
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
price_cents INT NOT NULL,
PRIMARY KEY (order_id, product_id)
);
CREATE TABLE carts (
user_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (user_id, product_id)
);
High-level architecture
Services
- Catalog service: Product CRUD; Elasticsearch for search; Postgres as source of truth.
- Cart service: Redis for active carts (fast reads); async persist to Postgres.
- Order service: Orchestrates checkout saga — inventory, payment, order creation.
- Inventory service: Atomic stock decrements with optimistic or pessimistic locking.
- Payment service: Stripe integration with idempotency keys; webhook for confirmation.
- Fulfilment service: Kafka consumer updates order status; notifies buyer via notification system.
Deep dive: inventory consistency
| Aspect | Optimistic locking (version column) | Pessimistic locking (SELECT FOR UPDATE) |
|---|---|---|
| Mechanism | UPDATE WHERE version = N; retry on conflict | Lock row during transaction |
| Throughput | High — no locks held | Lower — locks block concurrent buyers |
| Oversell risk | Zero if retry logic is correct | Zero if lock held until commit |
| StreamHub pick | Default for most products | Flash sales with extreme contention |
Mechanism
Optimistic locking (version column)UPDATE WHERE version = N; retry on conflictPessimistic locking (SELECT FOR UPDATE)Lock row during transactionThroughput
Optimistic locking (version column)High — no locks heldPessimistic locking (SELECT FOR UPDATE)Lower — locks block concurrent buyersOversell risk
Optimistic locking (version column)Zero if retry logic is correctPessimistic locking (SELECT FOR UPDATE)Zero if lock held until commitStreamHub pick
Optimistic locking (version column)Default for most productsPessimistic locking (SELECT FOR UPDATE)Flash sales with extreme contention
-- Optimistic inventory decrement
UPDATE products
SET inventory = inventory - 1, version = version + 1
WHERE id = 123 AND inventory > 0 AND version = 7;
-- If rows affected = 0 → out of stock or concurrent conflict → retry or fail
Deep dive: checkout saga
Saga steps with compensations
- Step 1: Reserve inventory (decrement stock) — compensate: increment stock back.
- Step 2: Charge payment via Stripe with idempotency key — compensate: refund.
- Step 3: Create order record in Postgres — compensate: mark order cancelled.
- Step 4: Publish
order.placedto Kafka — fulfilment, notification, analytics consume. - Failure at any step: Run compensations in reverse order; return clear error to buyer.
Quick recall
Everything you need if you only revisit this box.
- Catalog in Postgres + Elasticsearch; cart in Redis with Postgres backup.
- Checkout saga: reserve inventory → charge payment → create order → publish event.
- Optimistic locking (version column) prevents overselling without row-level locks.
- Idempotency-Key on checkout POST prevents double-charge on retry.
- Compensating transactions on saga failure: restock, refund, cancel order.
- 1M orders/day ≈ 12 QPS — Postgres handles this; inventory locking is the bottleneck.
Test yourself
Answer these before moving on — recall is what makes it stick.