Designing a Live Shopping Platform with Real-Time Inventory
A complete, ground-up architecture for a system where a creator sells products live on video, thousands of viewers watch and chat in real time, and every purchase must update inventory instantly and correctly — even when ten thousand people click “Buy” on the same limited-stock item within the same second.
Introduction and History
Imagine a shopping channel from the 1990s — a presenter holding up a blender, talking about it, and a phone number scrolling at the bottom of the screen for viewers to call and order. That idea, home shopping television, is decades old. What is new is what happens when you take that same idea and rebuild it on top of the internet, mobile phones, and modern distributed systems. Instead of a phone call, a viewer taps a screen. Instead of one broadcast reaching a few million TV sets, a stream can reach millions of phones anywhere in the world. And instead of a warehouse worker manually tracking how many blenders are left, a computer system has to know the exact stock count, update it in real time, and never let two people buy the last unit at the same time.
This combination — live video plus e-commerce plus real-time inventory — is usually called live shopping or live commerce. A creator or brand goes live on camera, shows off products, answers questions from viewers in a chat window, and viewers can buy those products without ever leaving the stream. The stream, the chat, the product catalog, and the checkout system all have to work together, in real time, at large scale.
Live commerce did not start in Silicon Valley. It became massive first in China, where platforms such as Taobao Live (part of Alibaba) turned livestream selling into a business worth many billions of dollars a year, with some individual streamers selling more merchandise in a single evening than a mid-size retail chain sells in a month. Seeing that success, western platforms followed: Amazon Live let sellers and influencers demonstrate products on video next to a live “buy” button; TikTok built TikTok Shop directly into its short-video and live-streaming app; Instagram and Facebook experimented with shopping tags inside live video; and newer, focused platforms like Whatnot built their entire business around live auctions and live selling for collectibles, fashion, and hobbyist communities.
From a systems point of view, live shopping is a fascinating problem because it forces three very different engineering worlds to work together at the same time:
Real-time video streaming
The same kind of low-latency video delivery problem that YouTube Live, Twitch, and video conferencing tools solve.
High-concurrency checkout
The same kind of “flash sale” problem that ticketing sites and sneaker drop sites solve, where thousands of people try to buy a handful of units at the exact same moment.
Real-time social interaction
Live chat, reactions, and viewer counts — the same kind of problem that chat applications and multiplayer games solve.
Think of live shopping as running a physical in-store demo, a public auction, and a cash register all at once — except the “store” has millions of people standing in it at the same moment, all reaching for the same three items on the shelf, and the cash register has to say “sold out” to everyone else the instant the last item is taken, without ever double-selling it.
In this tutorial we will design such a platform from scratch, piece by piece. We will cover the video pipeline, the microservices that manage products and orders, the message queue that connects everything, the caching layer that makes inventory checks fast, the database design that keeps orders and stock counts correct, and the operational concerns — security, monitoring, deployment — that keep the whole system running smoothly during a launch event that might attract a huge, sudden spike of traffic in the space of a few minutes.
Problem and Motivation
Before drawing any boxes and arrows, we need to be precise about what problem we are actually solving, and why it is hard. It is easy to say “let creators sell products on a livestream.” It is much harder to say exactly what breaks if we build this carelessly.
2.1 What makes this different from normal e-commerce?
A normal online store, like a typical product listing page, has a fairly gentle traffic pattern. People browse whenever they want, over hours or days. A live shopping event compresses all of that demand into a very short, very intense window — often just a few minutes — because the creator says on camera, “I have only 200 units of this jacket, link is live now,” and thousands of viewers, all watching the same video at the same moment, react at the same moment.
| Normal E-Commerce | Live Shopping |
|---|---|
| Traffic spreads out over hours/days | Traffic spikes in seconds, tied to a spoken moment on camera |
| Users browse independently | Users watch the same video frame, react together |
| Stock usually lasts hours | Limited-edition drops can sell out in under a second |
| No live social layer | Live chat, reactions, and viewer count must update in real time |
| Checkout latency of 1–2s is fine | Even small delays cause visible “still processing” frustration while stock disappears |
2.2 The core hard problems
The Overselling Problem
If 5,000 people click “Buy” on an item with 50 units left, at the exact same second, how do we guarantee that exactly 50 orders succeed and 4,950 people get an honest “sold out” — without the database falling over, and without accidentally selling 51 or 60 units because two servers both thought there was stock left?
The Low-Latency Video Problem
Video has to travel from the creator’s phone or camera, through the internet, to potentially millions of viewers’ phones, with only a few seconds of delay. If the delay is too large, viewers see the item highlighted long after it is already sold out, and they buy something that no longer exists.
The Chat & Engagement Scale Problem
A popular stream can have hundreds of thousands of concurrent viewers, all able to send chat messages, likes, and reactions. Broadcasting all of that to everyone, in real time, without melting the servers, is its own distributed systems challenge.
Consistency vs. Speed Tension
Strict correctness (never oversell) usually means locking or coordinating, which can slow things down. But slow checkout during a flash-sale moment is unacceptable. The design has to be both fast and correct.
“Everyone Is Watching the Same Clock”
Unlike a normal store, thousands of viewers are quite literally reacting to the same 2-second window of video. This creates a genuine thundering herd — a sudden, synchronized wave of demand — which is much harder to smooth out than ordinary organic traffic.
Picture a single ticket counter at a train station with 50 tickets left for a very popular train, and a loudspeaker just announced “last 50 tickets, on sale now” to a crowd of 5,000 people simultaneously. Everyone rushes the counter at once. A good system is the equivalent of instantly forming a fair, fast-moving queue that hands out exactly 50 tickets and turns everyone else away politely and immediately — not a crowd crush at a single window.
2.3 Business motivation
Beyond the technical challenge, the business case is strong: live shopping combines entertainment and commerce (“shoppertainment”), which tends to produce far higher conversion rates than static product pages, because viewers get real-time answers to their questions, social proof from other viewers buying at the same time, and a sense of urgency from limited stock and a ticking clock. Getting the system right directly translates into revenue; getting it wrong — a stream that lags, a checkout that fails, or a stock count that is wrong — directly damages trust and sales in a very visible, very public way, in front of a live audience.
“Why can’t we just use a normal e-commerce checkout flow for this?” A strong answer explains the compressed, synchronized demand spike unique to live events, and why classic optimistic web-scale patterns (eventual consistency, lazy stock updates) can cause visible overselling that is unacceptable when thousands of eyes are on the same countdown at once.
Requirements
3.1 Functional Requirements
Broadcast & Watch
Creators can start, run, and end a live video stream from a phone, camera, or streaming software. Viewers can join on web or mobile with minimal startup delay.
Spotlight Products
Creators can “pin” or “spotlight” a product during the stream, showing it as buyable to viewers.
Instant Buy
Viewers can buy a spotlighted product instantly, without leaving the stream, in a few taps.
Live Inventory
Inventory counts must update in real time and be visible to all viewers (“12 left” ticking down live).
Chat & Engagement
Viewers can chat, react (likes/hearts), and see a live viewer count. Creators and moderators can moderate chat.
Order Integration
Orders integrate with payment processing, shipping, and order-history systems.
Replay (VOD)
Stream replays should be available after the live event ends, ideally still shoppable.
Analytics
Creators and platform operators need real-time dashboards of viewers, sales, and conversion.
3.2 Non-Functional Requirements
| Requirement | Target | Why it matters |
|---|---|---|
| Video glass-to-glass latency | 2–5 seconds | Viewers must see the same “moment” as everyone else, close to real time |
| Inventory accuracy | Zero overselling, ever | Overselling means broken promises, refunds, and reputational damage |
| Checkout response time | < 300ms p99 for the buy action | Flash-sale UX must feel instant or users assume it failed and retry, worsening load |
| Concurrent viewers per stream | Up to millions | Viral streams and celebrity creators can draw huge simultaneous audiences |
| Availability | 99.95%+ during live events | A live event cannot be “rescheduled” if the system goes down mid-stream |
| Chat/reaction fan-out | Sub-second delivery to all viewers | Chat that arrives late feels broken and kills engagement |
| Consistency for orders/payments | Strong consistency (ACID) | Money and stock cannot be “eventually correct” — they must be exactly correct |
| Consistency for viewer count/chat | Eventual consistency is fine | Being off by a few viewers or a chat message arriving 200ms late causes no real harm |
Not every part of this system needs the same consistency guarantee. Money and stock need strong consistency. Viewer counts and “likes” can be approximate and eventually consistent. Recognizing which parts need which guarantee is the single most important decision in this whole design — and it is exactly the kind of judgment call interviewers want to see you make explicitly, rather than applying one consistency model everywhere.
3.3 Back-of-the-envelope scale estimation
Let’s reason about scale, since it drives almost every later decision:
0.5M–2M
Concurrent viewers on a single popular stream for a viral celebrity event.
10s of K/sec
Peak “buy” clicks in the first second after a product drop for a hyped, limited-stock item.
1000s/sec
Chat messages per second on a huge stream at peak; after fan-out to all viewers, tens of millions of message deliveries per second.
100s–1000s
Number of concurrent live streams platform-wide, each independently scaling.
These numbers tell us immediately that we cannot rely on a single database instance for inventory, we cannot broadcast chat by looping over a list of users in application code, and we cannot serve video by streaming directly from the creator’s device to every viewer — we need a CDN.
Architecture and Components
Let’s now design the system piece by piece. We will split it into three cooperating “planes”: the video plane (getting the creator’s camera feed to millions of viewers), the commerce plane (product catalog, inventory, orders, payments), and the real-time engagement plane (chat, reactions, live viewer counts). All three sit behind a shared edge layer of load balancers, API gateway, and CDN.
4.1 High-Level Architecture Diagram
Every box below is labeled with exactly what kind of component it is, so the role of each piece in the request path is unambiguous.
At first glance this looks like a lot of boxes, but each one exists to solve exactly one of the hard problems from Section 2. Let’s walk through every component, what it is, why it exists, and how it maps to something familiar.
4.2 Edge Layer
4.2.1 CDN (Content Delivery Network)
What it is: A globally distributed network of edge servers that cache and serve content physically close to each viewer.
Why it exists: Video is heavy. If every viewer pulled video directly from our origin servers, our data center’s network link would be overwhelmed in seconds by a stream with a million viewers. A CDN instead lets each viewer download video segments from a nearby edge node, so our origin only needs to serve a handful of edge nodes, not millions of viewers directly.
Instead of one water tank supplying every house in a country directly through one pipe, you build local water towers in every city, filled from the main tank, so each house draws from a nearby tower. The main tank only needs to keep the towers full, not serve every house individually.
In this system: The CDN caches the video’s HLS/DASH segments and also serves static assets like product images. Popular CDNs used for this include Akamai, Cloudflare, Amazon CloudFront, and Fastly.
4.2.2 Load Balancer (Layer 7)
What it is: A component that sits in front of a group of servers and distributes incoming requests across them, using application-level information (HTTP path, headers) since we labeled it Layer 7.
Why it exists: No single server can handle millions of requests. The load balancer spreads traffic evenly, performs health checks to route around failed instances, and terminates TLS so backend services don’t each need to manage certificates.
A restaurant host who looks at how busy each section of the restaurant is, and seats new guests at whichever table’s server currently has capacity, instead of guests randomly choosing a table and overloading one section.
In this system: NGINX or Envoy sit at the edge, terminating TLS and load-balancing across API Gateway instances. During a flash-sale spike, the load balancer’s health checks and connection-draining logic keep failing instances out of rotation immediately.
4.2.3 API Gateway
What it is: A single, unified entry point for all client requests into the backend microservices.
Why it exists: Without a gateway, every client would need to know the address of every individual microservice, and every microservice would need to reimplement authentication, rate limiting, and request logging. The gateway centralizes these cross-cutting concerns.
A hotel’s front desk. Guests don’t wander the building looking for housekeeping or room service directly — they go to the front desk, which checks who they are (authentication) and routes their request to the right department.
In this system: The API Gateway authenticates the viewer’s session token, applies per-user and per-IP rate limiting (critical during a “buy now” surge to stop bots and retries from overwhelming the Order Service), and routes /catalog/* to the Catalog Service, /inventory/* to the Inventory Service, /orders/* to the Order Service, and upgrades /chat connections to WebSocket and routes them to the Chat Service.
“Why put both a load balancer and an API gateway in the path — isn’t that redundant?” A good answer: the load balancer operates at the transport/HTTP routing level across gateway replicas for availability and scaling; the API gateway operates at the application level, handling auth, rate limiting and business routing. They solve different problems and commonly coexist in production systems.
4.3 Video Plane Components
4.3.1 Stream Ingest Service
What it is: The entry point that receives the live video/audio feed from the creator’s camera or streaming software, typically over the RTMP or WebRTC protocol.
Why it exists: The creator’s raw camera feed needs a dedicated, always-available receiving endpoint that can handle unreliable mobile networks, reconnect gracefully, and hand the stream off for processing.
A television studio’s satellite uplink receiver — the first point where a broadcast truck’s signal enters the professional broadcast pipeline.
Production example: Twitch and YouTube Live use dedicated ingest server fleets distributed by geography so a creator connects to a nearby ingest point, minimizing upload latency before transcoding even starts.
4.3.2 Transcoding Service
What it is: Converts the single incoming high-bitrate stream into multiple quality “renditions” (for example 1080p, 720p, 480p, 240p).
Why it exists: Viewers have wildly different network conditions — a viewer on fast Wi-Fi and a viewer on patchy mobile data cannot both be served the exact same video bitrate. Transcoding produces several versions so the player on each device can pick (and dynamically switch) the best one, a technique called adaptive bitrate streaming (ABR).
A publisher printing the same book in hardcover, paperback, and large-print editions, so each reader can pick the format that fits their needs, all from one original manuscript.
4.3.3 Packaging Service
What it is: Slices each transcoded rendition into small segments (typically 2–6 seconds) and produces a manifest file, packaged as HLS (HTTP Live Streaming) or DASH (Dynamic Adaptive Streaming over HTTP).
Why it exists: Video players can’t efficiently stream one giant continuous file over HTTP. Breaking video into small segments lets players fetch, cache, and switch quality level segment-by-segment, and lets the CDN cache each small segment independently.
4.4 Commerce Plane Components
4.4.1 Product Catalog Service
What it is: Owns product data — titles, descriptions, images, prices, variants (size/color) — and serves fast reads to the client and to other services.
Why it exists: Product data changes rarely compared to inventory, and is read constantly (every viewer loads product cards). Separating it from inventory lets us cache it aggressively without worrying about staleness on the number that actually matters for correctness (stock count).
A restaurant’s printed menu (catalog: dish name, description, price) versus the kitchen’s live count of how many portions of today’s special are left (inventory). The menu can be printed once and reused all night; the count of remaining portions must be checked fresh every time.
4.4.2 Inventory Service
What it is: The single source of truth for how many units of each product/variant are available, and the component responsible for atomically reserving and decrementing stock as orders are placed.
Why it exists: This is the most safety-critical service in the whole system. It exists as an isolated service (rather than a field on the Catalog Service) specifically so it can be optimized, scaled, and locked down for one job: never letting stock go negative, no matter how much concurrent load hits it.
We will look at exactly how it achieves atomic decrements under extreme concurrency in Section 5.
4.4.3 Order Service
What it is: Manages the lifecycle of an order — created, payment pending, paid, fulfilled, cancelled, refunded — and orchestrates the multi-step process of turning a “Buy” click into a confirmed purchase.
Why it exists: A purchase touches multiple systems (inventory, payment, shipping). The Order Service coordinates these steps as a single logical transaction using patterns like Saga (explained in Section 15), so that a failure partway through — for example, a payment decline after inventory was reserved — is always cleanly rolled back.
4.4.4 Payment Service
What it is: Handles charging the customer’s payment method, typically by calling out to a PCI-compliant third-party payment processor (Stripe, Razorpay, Adyen, PayPal) rather than handling raw card data itself.
Why it exists: Isolating payment logic into one service shrinks the “PCI compliance boundary” — the part of the system that must meet strict card-data security standards — to the smallest possible surface area, instead of spreading sensitive payment handling across the whole codebase.
4.4.5 Shipping / Fulfillment Service
What it is: Triggered after a successful payment, this service creates fulfillment tasks — packing, warehouse pick lists, courier handoff, tracking numbers.
Why it exists: Fulfillment is inherently asynchronous (a warehouse worker packs a box over minutes or hours, not milliseconds) so it is decoupled from the fast, synchronous checkout path via the message broker.
4.5 Real-Time Engagement Plane
4.5.1 Chat Service (WebSocket Gateway)
What it is: Manages persistent WebSocket connections with every connected viewer, receiving chat messages and reactions and fanning them out to all other viewers of that stream.
Why it exists: HTTP request/response is the wrong model for “many people need to see the same message within milliseconds.” WebSockets keep an open, bidirectional connection so the server can push messages instantly instead of clients repeatedly polling.
A walkie-talkie channel that everyone in the group is tuned into, versus repeatedly calling each person’s phone to read them the latest message.
Scaling note: Chat servers are typically organized so all connections for a given stream’s “room” are grouped, and a pub/sub layer (Redis Pub/Sub or a Kafka topic per popular room) fans messages out across many chat server instances, since a single server cannot hold a million concurrent WebSocket connections for one viral stream.
4.5.2 Presence / Viewer-Count Service
What it is: Tracks who is currently watching each stream and produces the “12,458 watching” counter.
Why it exists: An exact, strongly consistent count would require coordinating every single connect/disconnect event, which is unnecessary overhead for a number that is inherently approximate and constantly changing. This service uses approximate counting (for example, periodically aggregated counts from each chat server shard) which is far cheaper and entirely acceptable for this use case.
4.5.3 Notification Service
What it is: Sends push notifications, SMS, or email — for example, “Your favorite creator just went live,” or “Your order has shipped.”
Why it exists: Like fulfillment, notifications are asynchronous and non-critical-path, so they are triggered by events on the message broker rather than blocking any user-facing request.
4.6 Shared Infrastructure
4.6.1 Message Broker (Kafka)
What it is: A distributed, append-only log system that lets services publish events (like “order placed” or “stock updated”) and lets other services subscribe and react, without the publisher needing to know who is listening.
Why it exists: Without an event bus, every service would need direct point-to-point calls to every other interested service, creating a tangled, fragile web of synchronous dependencies. The broker decouples producers from consumers, absorbs bursts of traffic (acting as a buffer), and lets us add new consumers (like a new analytics pipeline) without touching existing services.
A public notice board in a village square. Someone posts a notice (“new item in stock”) once; anyone interested walks by and reads it whenever they want, without the notice-poster needing to individually tell every villager.
4.6.2 Cache Cluster (Redis)
What it is: An in-memory key-value data store used to hold frequently accessed, latency-sensitive data — hot product info, session tokens, and critically, live inventory counters.
Why it exists: Reading and writing a stock counter from a disk-based relational database, thousands of times per second, is far too slow for a flash-sale moment. Redis keeps the “hot” numbers in memory and supports atomic operations, which we rely on heavily in Section 5.
4.6.3 Observability Stack
What it is: The combination of metrics collection (Prometheus), dashboards (Grafana), distributed tracing (OpenTelemetry/Jaeger), and centralized logging (the ELK stack: Elasticsearch, Logstash, Kibana).
Why it exists: With a dozen microservices cooperating on a single purchase, when something goes wrong you need to trace one request’s full journey across every service it touched — this is covered fully in Section 11.
Internal Working
Now let’s zoom into the two hardest internal mechanics: how a “Buy” click is processed without overselling, and how the video actually gets from a phone camera to a million screens with only a few seconds of delay.
5.1 The Purchase Path, Step by Step
- Viewer taps “Buy” on the spotlighted product inside the stream player.
- Request hits the Load Balancer, then the API Gateway, which checks the auth token and applies rate limiting.
- The Order Service receives the purchase intent and calls the Inventory Service to reserve one unit — not yet a full decrement, but a short-lived hold (see 5.2).
- If the reservation succeeds, the Order Service creates a pending order record and calls the Payment Service to charge the customer.
- If payment succeeds, the Order Service confirms the reservation (now a real decrement) and marks the order as paid; it publishes an
OrderPlacedevent to Kafka. - If payment fails, the Order Service releases the reservation back to available stock, and the order is marked failed.
- Downstream consumers of the
OrderPlacedevent (Shipping Service, Notification Service, Analytics Warehouse) react independently and asynchronously. - The updated stock count is pushed to all viewers of that stream via the Presence/Chat pub-sub channel, so everyone sees “3 left” tick down live.
5.2 Solving the Overselling Problem: Atomic Reservation
The naive approach — “read the stock count, check if it’s greater than zero, then write count-1” — is broken under concurrency. If a thousand requests all read “5” before any of them writes, all thousand may believe they succeeded. This is called a race condition, and it’s exactly what causes overselling.
The fix is to make the check-and-decrement a single, indivisible (atomic) operation, so no two requests can ever interleave their reads and writes. Redis makes this simple with a Lua script, which Redis guarantees runs atomically, from start to finish, with no other command executing in between:
-- KEYS[1] = inventory key, e.g. "inventory:product:12345"
-- Returns: 1 if reserved successfully, 0 if out of stock, -1 if key missing
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock == nil then
return -1 -- product not found in cache
end
if stock > 0 then
redis.call('DECR', KEYS[1])
return 1
else
return 0
end
Here is how the Inventory Service (written in Java, using the Spring framework and Lettuce/Jedis Redis client) calls this script atomically:
@Service
public class InventoryReservationService {
private final RedisScript<Long> decrementScript;
private final StringRedisTemplate redisTemplate;
private final KafkaTemplate<String, InventoryEvent> kafkaTemplate;
public InventoryReservationService(StringRedisTemplate redisTemplate,
KafkaTemplate<String, InventoryEvent> kafkaTemplate) {
this.redisTemplate = redisTemplate;
this.kafkaTemplate = kafkaTemplate;
this.decrementScript = RedisScript.of(
new ClassPathResource("scripts/atomic-decrement.lua"), Long.class);
}
public ReservationResult reserve(String productId, String orderId) {
String key = "inventory:product:" + productId;
Long result = redisTemplate.execute(decrementScript, List.of(key));
if (result == null || result == -1L) {
throw new ProductNotCachedException(productId);
}
if (result == 0L) {
return ReservationResult.OUT_OF_STOCK;
}
// Reservation succeeded - record a short-lived hold so we can
// roll back cleanly if payment fails.
String holdKey = "hold:" + orderId;
redisTemplate.opsForValue().set(holdKey, productId, Duration.ofMinutes(2));
// Publish so live viewers see the count drop.
kafkaTemplate.send("inventory-events",
new InventoryEvent(productId, InventoryEventType.RESERVED, orderId));
return ReservationResult.RESERVED;
}
public void release(String orderId) {
String holdKey = "hold:" + orderId;
String productId = redisTemplate.opsForValue().get(holdKey);
if (productId != null) {
redisTemplate.opsForValue().increment("inventory:product:" + productId);
redisTemplate.delete(holdKey);
kafkaTemplate.send("inventory-events",
new InventoryEvent(productId, InventoryEventType.RELEASED, orderId));
}
}
}
Why does this scale so well? Because Redis is single-threaded for command execution on a given key, every DECR-style operation against one key is naturally serialized — there is no possibility of two requests interleaving on the same counter, and no explicit application-level lock is needed at all.
Doing the stock check in application code with a separate GET followed by a separate SET, even against Redis. That reintroduces the race condition, because another request’s write can slip in between your GET and your SET. Always use an atomic primitive (Lua script, or Redis’s built-in DECR/WATCH/transactions) for check-and-decrement logic.
5.3 Keeping Redis and the Database in Sync
Redis gives us speed, but it is a cache, not the ultimate source of truth for financial-grade correctness — a cache node could restart and lose data. So the design uses a two-tier approach:
- Fast path (hot): Redis atomic decrement handles the flash-sale-speed reservation check.
- Durable path (source of truth): The Order Service, once payment succeeds, writes the final, authoritative decrement into the relational database inside the same transaction as the order confirmation, using optimistic locking as a second safety net.
Here’s the JPA entity with optimistic locking as a defense-in-depth safety net, in case Redis and the database ever drift (for example after a cache failover):
@Entity
@Table(name = "inventory")
public class InventoryRecord {
@Id
private String productId;
@Column(nullable = false)
private int stockCount;
@Version // JPA optimistic locking column
private long version;
public void decrementStock() {
if (stockCount <= 0) {
throw new InsufficientStockException(productId);
}
this.stockCount -= 1;
}
// getters/setters omitted
}
@Service
public class InventoryPersistenceService {
@Retryable(value = OptimisticLockException.class, maxAttempts = 5,
backoff = @Backoff(delay = 20, multiplier = 2))
@Transactional
public void confirmDecrement(String productId) {
InventoryRecord record = inventoryRepository.findById(productId)
.orElseThrow(() -> new ProductNotFoundException(productId));
record.decrementStock();
inventoryRepository.save(record); // throws OptimisticLockException on version conflict
}
}
If two transactions somehow race at the database layer, JPA’s @Version column causes the losing transaction’s commit to fail with an OptimisticLockException, which we retry with exponential backoff. In practice, because Redis already serialized the reservation upstream, this database-level conflict should be rare — it exists purely as a correctness backstop, not the primary defense.
5.4 The Video Path: Glass-to-Glass
“Glass-to-glass latency” means the time from light hitting the creator’s camera lens to light leaving the viewer’s screen. Here is that path:
Two techniques keep this fast: using small segment sizes (so the player doesn’t wait for a large chunk to finish encoding before it can start playing), and using WebRTC instead of RTMP for the sub-second-latency “interactive” tier some platforms offer for creator-to-moderator communication, while the bulk of viewers still consume the slightly higher-latency but far more scalable HLS/DASH path through the CDN.
“Why not deliver video to every viewer directly over WebRTC for the lowest possible latency?” WebRTC peer connections don’t scale well to millions of simultaneous viewers because each connection consumes dedicated server resources; HLS/DASH over a CDN scales to massive audiences because segments are cacheable, shareable files, at the cost of a few extra seconds of latency. Most live shopping platforms accept 2–5s in exchange for that scalability.
Data Flow and Lifecycle
Let’s trace one complete “life of a purchase” as a sequence diagram, showing every component from Section 4 working together.
6.1 The Rollback Path (Payment Failure)
Correctness also requires handling the unhappy path cleanly. If payment fails after inventory was reserved, we must release the hold immediately so the unit becomes available to the next viewer:
Note that the two-minute hold TTL set on the Redis key in Section 5.2 acts as a safety net too: even if the Order Service crashes mid-flow and never explicitly calls release(), the hold key expiring automatically frees the reservation, and a background reconciliation job re-adds the unit to the visible stock count if it detects an orphaned hold.
6.2 Product Lifecycle During a Live Event
| State | Trigger | Visible to viewers as |
|---|---|---|
| Scheduled | Creator adds product to stream lineup before going live | Not shown yet |
| Spotlighted | Creator taps “feature this product” on camera | Buy button + live stock count appear |
| Selling | Orders being placed | Stock count ticking down in real time |
| Sold Out | Stock count reaches 0 | Buy button disabled, “Sold Out” badge |
| Unspotlighted | Creator moves to next product | Removed from active buy panel, still browsable |
Advantages, Disadvantages and Trade-offs
7.1 Advantages of This Architecture
Isolation of the safety-critical path
Because Inventory and Order are separate, focused services, we can lock down, test, and scale exactly the part of the system that must never be wrong, without over-engineering the rest.
Independent scaling
Video traffic, chat traffic, and checkout traffic scale completely differently during an event. Splitting them into separate services means we can add ten times more chat server capacity without touching the Order Service at all.
Resilience through decoupling
If the Shipping Service is temporarily down, purchases still succeed — the Kafka event simply waits in the topic until Shipping recovers. The customer-facing checkout path is not blocked by a slower downstream step.
Reusable video pipeline
The ingest/transcode/package pipeline is generic; it doesn’t know or care about products or checkout, so the same pipeline can power live shopping, regular live streaming, or even video-on-demand replays.
7.2 Disadvantages and Costs
Operational complexity
A dozen independently deployable services, a message broker, a cache cluster, and a CDN is a lot more moving parts than a single monolithic web app — more things that can fail, more infrastructure to monitor.
Eventual consistency surprises
Because chat, presence, and cross-service state propagate via events, there is always a small window (milliseconds, but nonzero) where different parts of the system see slightly different pictures of reality — for example, the stock counter briefly showing “1 left” to one viewer after it has actually sold out for another.
Cross-service latency
A purchase touches Order, Inventory, and Payment services in sequence — each network hop adds latency compared to a single monolithic database transaction.
Testing & debugging difficulty
Reproducing a bug that only appears under real flash-sale-level concurrency, across several services, is much harder than debugging a single-process application.
7.3 Key Trade-off Decisions
| Decision | Chosen approach | Alternative | Why we chose this |
|---|---|---|---|
| Inventory consistency | Strong: Redis atomic ops + DB optimistic locking | Eventually-consistent counters | Overselling is unacceptable; correctness beats raw throughput here |
| Viewer count | Approximate, eventually consistent | Exact real-time count via central coordinator | Exactness is not needed and would be far too costly at scale |
| Video delivery | CDN-based HLS/DASH, 2–5s latency | Direct WebRTC to every viewer, sub-second latency | CDN scales to millions; direct WebRTC to millions of viewers is not feasible |
| Service communication | Event-driven (Kafka) for non-critical-path steps | Synchronous calls everywhere | Keeps the checkout critical path fast; failures downstream don’t block purchase |
| Payment handling | Delegate to third-party PCI-compliant processor | Build in-house card processing | Drastically reduces compliance burden and security risk |
Performance and Scalability
8.1 Scaling the Video Path
CDN edge caching
The vast majority of viewer requests never reach our origin infrastructure at all — they are served from geographically nearby CDN edge nodes, which is the single biggest scalability lever for video.
Adaptive bitrate
By offering multiple renditions, we avoid wasting bandwidth on viewers with weak connections and avoid degrading quality unnecessarily for viewers with strong ones.
Horizontal scaling of transcoding
Transcoding is CPU/GPU intensive; the Transcoding Service runs as a horizontally scalable, stateless fleet, often using hardware-accelerated encoding (NVIDIA GPUs or dedicated ASICs) to process many concurrent streams.
8.2 Scaling the Checkout Path
Sharding inventory by product ID
Since each product’s stock counter lives at an independent Redis key, we can shard the Redis cluster by product ID (using Redis Cluster’s hash slots) so that a hot product’s traffic is isolated to specific shards rather than overwhelming a single node — and different hot products spread naturally across different shards.
Read/write separation for Catalog
Product reads (which vastly outnumber writes) are served from cache and read replicas; writes go to the primary.
Queueing at the edge for extreme drops
For an ultra-hyped, ultra-limited drop (say, 50 units against a million interested viewers), some platforms add a lightweight “virtual waiting room” in front of checkout — a fair, first-come queue implemented with a Redis sorted set — so the Order Service only ever receives a manageable, throttled stream of purchase attempts instead of the entire spike at once.
Rate limiting & idempotency keys
The API Gateway rate-limits repeated “Buy” taps from the same user (common when users panic-tap because the UI feels slow), and every purchase request carries a client-generated idempotency key so a retried request never creates a duplicate order.
8.3 Scaling Chat and Presence
Room-based sharding
Each stream’s chat is its own “room,” and rooms are distributed across many Chat Service instances, so one viral stream’s chat load doesn’t need to fan out from a single process.
Pub/Sub fan-out
A message posted to a room is published once to a Redis Pub/Sub channel (or a Kafka topic) and every Chat Service instance holding connections for that room subscribes and pushes to its local WebSocket connections — this avoids the sender needing to know about every individual connection.
Sampling for extreme scale
On the very largest streams, some platforms deliberately sample or batch chat messages (showing a representative subset rather than literally every message to every viewer) to keep client-side rendering and network usage manageable.
Instead of one supermarket checkout line for the entire city, you open many checkout lanes, and assign each shopper to a lane based on some rule (like their cart’s barcode range). Each lane handles its own smaller crowd independently, so the whole system moves faster than if everyone queued at a single register.
“How would you handle a single product that is so hyped it becomes a hot key even after sharding?” Good answers mention: further splitting that single product’s counter into N sub-counters (e.g., 10 counters of 5 units each) that requests are randomly assigned to, reducing contention on any one key, then reconciling at the end; or introducing a queue/waiting-room in front of that specific product.
High Availability and Reliability
9.1 Redundancy at Every Layer
| Layer | HA strategy |
|---|---|
| CDN | Globally distributed edge nodes; automatic failover to next-nearest edge on node failure |
| Load Balancer | Deployed in active-active pairs across availability zones |
| Microservices | Multiple stateless replicas per service, spread across zones, behind health-checked load balancing |
| Redis cache | Redis Cluster with primary-replica shards; automatic failover promotes a replica if a primary node dies |
| Order DB (PostgreSQL) | Primary with synchronous standby replica for zero data loss on failover, plus asynchronous cross-region replicas for disaster recovery |
| Kafka | Multi-broker cluster with topic replication factor of 3, tolerating broker failures without data loss |
| Stream ingest | Creator app configured with a backup ingest endpoint in a second region if primary ingest is unreachable |
9.2 Resilience Patterns in Application Code
Even with redundant infrastructure, individual calls between services can still fail or time out. We use the circuit breaker pattern to prevent one failing dependency from cascading into a full outage:
@Service
public class PaymentClient {
private final CircuitBreaker circuitBreaker;
private final RestClient restClient;
public PaymentClient(CircuitBreakerRegistry registry, RestClient restClient) {
this.circuitBreaker = registry.circuitBreaker("payment-service");
this.restClient = restClient;
}
public PaymentResult charge(ChargeRequest request) {
Supplier<PaymentResult> call = CircuitBreaker
.decorateSupplier(circuitBreaker, () ->
restClient.post()
.uri("/v1/charges")
.body(request)
.retrieve()
.body(PaymentResult.class));
try {
return call.get();
} catch (CallNotPermittedException e) {
// Circuit is open - payment provider is unhealthy.
// Fail fast instead of piling up timeouts.
return PaymentResult.temporarilyUnavailable();
}
}
}
Configured with Resilience4j, this circuit breaker trips (“opens”) after a threshold of failures within a rolling window, immediately rejecting further calls to the failing payment provider for a cooldown period rather than letting every checkout request pile up waiting on a doomed call. This keeps the rest of the system responsive and gives the failing dependency room to recover.
9.3 Disaster Recovery
Multi-region deployment
Critical services run in at least two regions; DNS-based or global load balancer failover redirects traffic if an entire region becomes unavailable.
Database backups & PITR
Continuous WAL (write-ahead log) archiving for PostgreSQL enables restoring the Order DB to any point in time, protecting against data corruption, not just hardware failure.
Runbooks & game days
Teams regularly simulate failures (a “game day”) — killing a Redis node, a Kafka broker, or an entire region — during low-traffic periods to verify failover actually works before a real high-stakes live event depends on it.
9.4 Graceful Degradation
Not every failure needs to become a full outage. The system is designed so lower-priority features degrade first, protecting the core “watch and buy” path:
- If the Chat Service is overloaded, chat can be temporarily throttled or paused while video and checkout continue uninterrupted.
- If the recommendation/personalization service is down, viewers still see the default product lineup instead of a broken page.
- If analytics ingestion (Kafka to the warehouse) lags, dashboards show slightly stale numbers, but no customer-facing functionality is affected.
“During a huge live event, the Payment Service’s third-party provider starts timing out. What happens?” Expect a candidate to describe the circuit breaker opening, requests failing fast with a clear “try again” message instead of hanging, and possibly routing to a secondary payment provider if one is configured, rather than the whole Order Service backing up and becoming unresponsive.
Security
10.1 Authentication and Authorization
- OAuth2 / OpenID Connect for viewer and creator login, issuing short-lived JWT access tokens plus longer-lived refresh tokens.
- API Gateway as the enforcement point: every request’s JWT is validated at the gateway before it ever reaches a backend microservice, so individual services can trust the identity context passed to them rather than each re-implementing token validation.
- Role-based access control (RBAC): distinguishes viewer, creator, moderator, and platform-admin permissions — for example, only a creator or moderator for a given stream can remove chat messages or ban a user in that room.
10.2 Payment and PCI-DSS
Raw card numbers never touch our own servers. The client-side SDK (e.g., Stripe.js/Elements) tokenizes card details directly with the payment processor in the browser or app, and our Payment Service only ever handles that opaque token, never the underlying card number. This keeps almost the entire platform out of PCI-DSS’s strictest compliance scope (SAQ A rather than the far more demanding SAQ D).
10.3 Protecting the Flash-Sale Moment from Abuse
Bot & scalper mitigation
Rate limiting per user/IP/device fingerprint on the “Buy” endpoint, CAPTCHA challenges triggered by suspicious velocity, and purchase-limit-per-account rules (e.g., max 2 units of a hyped item per customer) to keep drops fair for real viewers rather than automated scripts.
Idempotency keys
Prevent a retried or replayed request from creating a duplicate charge or double-decrementing stock.
DDoS protection
The CDN and edge layer absorb volumetric attacks before they ever reach origin infrastructure; the API Gateway applies stricter rate limits during detected anomalous traffic patterns.
10.4 Content and Chat Moderation
- Automated profanity/spam/hate-speech filtering on chat messages before broadcast, backed by a moderation ML model or third-party moderation API.
- Creator/moderator tools to mute, ban, or slow-mode chat during a stream.
- Video content moderation (automated flagging for policy violations) for creators, especially relevant for platforms open to a wide range of independent sellers.
10.5 Data Protection
- Encryption in transit (TLS everywhere, including between internal services in stricter deployments — mTLS) and encryption at rest for databases holding personal and payment-related data.
- Field-level encryption or tokenization for sensitive personally identifiable information (shipping addresses, phone numbers).
- Strict data retention and deletion policies to comply with privacy regulations (such as GDPR in Europe or India’s DPDP Act), including honoring user data-deletion requests across every service that stores their data.
Treating rate limiting only as a performance concern. During a live shopping drop, aggressive automated buying scripts are a real security and fairness problem, not just a load problem — the rate limiting and abuse-detection layer should be designed with that adversarial mindset from day one.
Monitoring, Logging and Metrics
11.1 What We Must Watch During a Live Event
| Metric | Why it matters | Tooling |
|---|---|---|
| Video startup time & buffering ratio | Directly affects whether viewers stay watching (and buying) | Real-user monitoring (RUM) from the video player SDK |
| Checkout p50/p95/p99 latency | Slow checkout during a drop causes retries, which worsens load further | Prometheus histograms, Grafana dashboards |
| Inventory reservation error rate | Spikes may indicate a Redis hot-key or cluster issue | Custom application metrics + alerting |
| Order-to-payment success ratio | Drops may indicate a payment provider outage | Business metrics dashboard, alerting on threshold breach |
| WebSocket connection count & message fan-out latency | Indicates chat/presence system health under load | Per-instance connection metrics, Prometheus |
| Kafka consumer lag | Growing lag means downstream systems (shipping, notifications, analytics) are falling behind | Kafka’s built-in consumer group metrics, Burrow/Kafka Exporter |
11.2 Distributed Tracing
A single purchase touches the Gateway, Order, Inventory, Payment, and database — tracing that whole journey with one shared trace ID is essential for debugging. Using OpenTelemetry, every service propagates trace context automatically:
@RestController
public class OrderController {
private static final Tracer tracer =
GlobalOpenTelemetry.getTracer("order-service");
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest req) {
Span span = tracer.spanBuilder("createOrder")
.setAttribute("product.id", req.getProductId())
.setAttribute("user.id", req.getUserId())
.startSpan();
try (Scope scope = span.makeCurrent()) {
ReservationResult reservation = inventoryClient.reserve(req);
span.setAttribute("reservation.result", reservation.name());
if (reservation != ReservationResult.RESERVED) {
span.setStatus(StatusCode.ERROR, "out_of_stock");
return ResponseEntity.status(409).body(OrderResponse.outOfStock());
}
PaymentResult payment = paymentClient.charge(req);
span.setAttribute("payment.result", payment.name());
// ... persist order, publish event ...
return ResponseEntity.ok(OrderResponse.from(payment));
} finally {
span.end();
}
}
}
With this in place, when a customer reports “my buy button spun forever,” an engineer can pull up the exact trace for that request ID and see precisely which service or downstream call was slow — the Inventory reservation, the Payment provider, or the database write — instead of guessing.
11.3 Alerting Philosophy
- Alert on symptoms that affect customers (checkout latency, error rate, video buffering) rather than only on low-level infrastructure metrics (CPU, memory) that don’t necessarily indicate customer impact.
- Use multi-window, multi-burn-rate alerting (based on Google’s SRE practices) so a brief blip doesn’t page an engineer at 2 a.m., but a sustained problem during a live event pages immediately.
- Maintain a real-time “event health” dashboard specifically for the duration of a scheduled major live shopping event, watched actively by an on-call team, since these events are pre-planned and high-stakes.
Deployment and Cloud
12.1 Container Orchestration
All stateless microservices (Order, Inventory, Catalog, Chat, Payment client, Notification) are packaged as containers and run on Kubernetes, which handles scheduling, health checking, and automatic restarts. Kubernetes’ Horizontal Pod Autoscaler (HPA) scales each service’s replica count based on real-time CPU, memory, or custom metrics (like queue depth or requests-per-second) — critical for handling the sudden spike when a live event begins.
# Simplified HPA config for the Order Service
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 10
maxReplicas: 500
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "200"
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 200
periodSeconds: 30
Note the aggressive scale-up policy (doubling capacity every 30 seconds with no stabilization delay) — appropriate here because live shopping traffic spikes are sudden and predictable in timing (the event has a scheduled start), so we would rather over-provision briefly than fall behind demand in the first critical seconds.
12.2 Pre-Warming for Scheduled Events
Because major live shopping events are scheduled in advance (unlike organic traffic spikes), the platform can proactively scale up capacity minutes before the event starts, rather than relying solely on reactive autoscaling that might not react fast enough for the first wave of a synchronized spike. This is a significant operational advantage live shopping has over unpredictable viral traffic.
12.3 Multi-Region Cloud Topology
- Video ingest and CDN: distributed globally by design (this is standard practice for any major cloud/CDN provider).
- Commerce services: deployed in multiple regions, with the Order DB primary in one region and read replicas in others, routing writes to the primary region and reads to the nearest replica.
- Data residency: for platforms operating across regulatory boundaries (e.g., needing to keep Indian users’ data within India under local data protection rules), region-pinned deployments and data stores keep specific customer data within required jurisdictions.
12.4 Progressive Delivery
New versions of services are rolled out using canary deployments — a new version first receives a small percentage (say 5%) of production traffic, monitored closely for error-rate or latency regressions, before gradually increasing to 100%, with automatic rollback if the canary’s metrics degrade. This is especially important for the Inventory and Order services, where a subtle bug could cause real financial and customer-trust damage if rolled out to 100% of traffic immediately — and deployments are typically frozen entirely during scheduled major live events as an extra safety measure.
Databases, Caching and Load Balancing
13.1 Choosing the Right Database for Each Job
| Data | Store | Why |
|---|---|---|
| Orders, payments, inventory ledger | Sharded PostgreSQL (relational) | Needs ACID transactions, strong consistency, and well-understood locking behavior for money and stock |
| Product catalog (title, description, images, variants) | Document store (MongoDB) | Flexible schema across many product types/categories, read-heavy, tolerant of eventual consistency |
| Product/creator search & discovery | Elasticsearch | Full-text search, faceted filtering (price range, category, in-stock only) at low latency |
| Hot inventory counters, sessions, product cache | Redis (in-memory) | Sub-millisecond atomic operations, essential for the flash-sale checkout path |
| Analytics & historical reporting | Cloud data warehouse (BigQuery/Snowflake) | Optimized for large-scale aggregation queries over historical event data, decoupled from live traffic |
13.2 Sharding the Order Database
The Order DB is sharded by a key such as user_id or region, so that no single database instance needs to handle the entire platform’s order volume. Each shard is a fully independent PostgreSQL primary/replica pair. A lightweight routing layer in the Order Service determines which shard owns a given order based on the shard key, consistent with typical horizontal partitioning strategy used across large-scale e-commerce systems.
13.3 Caching Strategy
- Cache-aside for product data: the Catalog Service checks Redis first; on a miss, it reads from MongoDB and populates the cache, with a time-to-live (TTL) to bound staleness.
- Write-through for inventory: unlike product data, inventory writes go to Redis first (for the fast atomic check), then asynchronously reconciled to the database, as described in Section 5.3 — a deliberate exception to the usual cache-aside pattern because raw speed on the write path is the priority here.
- Cache invalidation on product update: when a creator edits a product’s price or description mid-stream, the Catalog Service publishes an invalidation event so all cache nodes drop the stale entry rather than waiting out the TTL.
Cache-aside is like checking a sticky note on your fridge before walking to the store — if the note (cache) is missing or old, you go to the store (database) and update the note. Write-through for inventory is different: you update the sticky note the instant something changes, and only later walk to the store to make sure its records agree, because you need everyone reading the note right now to see the truth immediately.
13.4 Load Balancing Deep Dive
| Load balancer type | Where used | Why |
|---|---|---|
| Layer 4 (transport-level, e.g. AWS NLB) | In front of the Redis cluster and Kafka brokers, and for raw WebSocket/RTMP connections | Very low overhead, connection-level routing, ideal for persistent connections and non-HTTP protocols |
| Layer 7 (application-level, e.g. Envoy/NGINX) | In front of the API Gateway and HTTP microservices | Can route based on URL path, headers, and cookies; supports retries, circuit breaking, and TLS termination |
| Global/DNS-based load balancing | Routing users to the nearest healthy region | Directs traffic to the closest, healthiest data center, reducing latency and enabling regional failover |
13.5 Consistent Hashing for Cache and Chat Sharding
Both the Redis cluster (for inventory keys) and the Chat Service’s room assignment rely on consistent hashing to distribute keys/rooms across nodes. Consistent hashing ensures that when a node is added or removed (scaling up for an event, or recovering from a failure), only a small fraction of keys need to move to a new node, rather than reshuffling almost everything — which would happen with naive modulo-based hashing.
APIs and Microservices
14.1 Choosing Protocols per Use Case
| Interaction | Protocol | Why |
|---|---|---|
| Client to API Gateway (buy, browse) | REST over HTTPS | Simple, cacheable, universally supported by web and mobile clients |
| Client chat/reactions/live stock updates | WebSocket | Persistent bidirectional connection needed for real-time push |
| Service-to-service calls (Order to Inventory, Order to Payment) | gRPC | Low-latency binary protocol, strongly typed contracts, built-in streaming support — well suited to internal high-throughput calls |
| Asynchronous cross-service events | Kafka (event-driven, not request/response) | Decouples producers and consumers; supports replay and multiple independent subscribers |
14.2 Example REST Contract: Reserve and Purchase
POST /api/v1/orders
Headers: Authorization: Bearer <jwt>, Idempotency-Key: <uuid>
Body:
{
"productId": "prod_98213",
"streamId": "stream_44210",
"quantity": 1,
"paymentMethodToken": "tok_abc123"
}
Response 200 OK:
{
"orderId": "ord_77123",
"status": "PAID",
"amount": 2499,
"currency": "INR"
}
Response 409 Conflict:
{
"error": "OUT_OF_STOCK",
"message": "This item just sold out."
}
14.3 Example gRPC Contract: Inventory Service
syntax = "proto3";
service InventoryService {
rpc Reserve (ReserveRequest) returns (ReserveResponse);
rpc Release (ReleaseRequest) returns (ReleaseResponse);
rpc GetStock (StockRequest) returns (stream StockUpdate); // server-streaming for live counts
}
message ReserveRequest {
string product_id = 1;
string order_id = 2;
}
message ReserveResponse {
bool reserved = 1;
int32 remaining_stock = 2;
}
The GetStock RPC uses gRPC’s server-streaming capability — a single call keeps a stream open and the server pushes new stock values as they change, which is how internal dashboard services and the Presence Service can get live inventory updates without polling.
14.4 Microservice Boundaries: Why Split It This Way?
Service boundaries in this design follow the principle of splitting along independent scaling needs and independent rates of change, not just “one service per database table.” Catalog changes rarely and is read-heavy; Inventory changes constantly and must be extremely fast and safe; Payment has unique compliance requirements; Chat has entirely different connection-lifecycle characteristics (long-lived WebSocket state) than the mostly-stateless REST services. Each boundary reflects a genuinely different operational profile, which is the right reason to draw a service line — splitting services just for the sake of “microservices” without a clear independent-scaling or independent-ownership reason tends to just add complexity without benefit.
“Would you combine the Order Service and Inventory Service into one service, since they’re so tightly coupled in the purchase flow?” A thoughtful answer acknowledges this is a legitimate design choice with real trade-offs: combining them reduces network hops and simplifies the transaction, but couples their deployment and scaling together, and blurs the safety-critical inventory logic with the broader, faster-changing order orchestration logic. Many real systems do keep them separate specifically to isolate inventory’s correctness-critical code from the rest of order processing, but it is a genuine trade-off, not a rule.
Design Patterns and Anti-Patterns
15.1 The Saga Pattern (Distributed Transactions)
A purchase spans three services (Inventory, Payment, Order) with no shared database transaction across them. The Saga pattern handles this by breaking the operation into a sequence of local transactions, each with a defined compensating action if a later step fails.
We use an orchestration-based saga, where the Order Service explicitly plays the role of orchestrator, calling each step in order and triggering compensations on failure — this is easier to reason about and debug than a choreography-based saga (where each service reacts to the previous service’s event with no central coordinator), which tends to work better for longer, more loosely-coupled workflows like post-payment fulfillment.
15.2 CQRS (Command Query Responsibility Segregation)
The Inventory Service’s write path (atomic Redis decrement) and read path (serving “N left” to millions of viewers) have very different requirements — writes need strict correctness and low contention, reads need to scale to enormous fan-out. CQRS separates these: writes go through the tightly controlled reservation path, while the “current stock” number shown to viewers is served from a separately scaled, cached read model that is updated via the Kafka InventoryUpdated event stream, rather than every viewer’s screen hitting the authoritative counter directly.
15.3 Event Sourcing for Inventory Auditability
Rather than only storing the current stock count, the Inventory Service also appends every reservation, confirmation, and release as an immutable event to Kafka. This gives a complete, replayable audit trail — essential for resolving disputes (“why does the count not match what I expected?”) and for reconstructing state if a bug ever requires rebuilding the cache from scratch.
15.4 Anti-Patterns to Avoid
If Order Service synchronously calls Inventory, which synchronously calls a Notification Service, which synchronously calls an Analytics Service, before returning success to the user, then the slowest link in that entire chain determines checkout latency, and any one failure blocks the purchase entirely. Non-essential steps (notifications, analytics) belong on the asynchronous event bus, not the synchronous checkout path.
Mobile networks are unreliable; a client may retry a “Buy” request that actually succeeded server-side but timed out on the response. Without an idempotency key, this creates duplicate orders and double-charges. Every mutating endpoint on the critical path must be idempotent.
Some teams build the “correct” database-only path first and bolt on caching later purely as a performance afterthought. For inventory specifically, the cache (Redis atomic operations) is not just a performance optimization — it is the primary correctness mechanism under load. Designing it in from the start avoids a painful late rearchitecture.
If the Chat Service and the Order Service both read and write the same database tables directly, a schema change for one team’s needs can silently break the other team’s service. Each service should own its data and expose it only through its API or published events.
Combining circuit breakers (Section 9.2) with bulkheads — dedicating separate, isolated thread/connection pools per downstream dependency — ensures that a slow Payment provider can’t exhaust the thread pool that the Order Service also needs for calling Inventory, keeping unrelated functionality healthy even while one dependency struggles.
Best Practices and Common Mistakes
16.1 Best Practices
Design for the spike, not the average
Provision and load-test for the peak-second demand of a hyped drop, not the average traffic across the whole stream — average numbers hide the exact moment that actually breaks systems.
Make the “Buy” button give instant feedback
Even before the server responds, the UI should visually acknowledge the tap immediately (a loading state), reducing the temptation for users to tap repeatedly, which would otherwise multiply load.
Separate correctness-critical paths
Inventory and payment logic deserve the most rigorous testing, code review, and monitoring in the entire system, precisely because mistakes there have direct financial and trust consequences.
Load test with realistic, synchronized patterns
A gradual ramp-up load test does not reveal how the system behaves under a genuine thundering-herd spike; load tests should specifically simulate thousands of simultaneous requests hitting the same product at the same instant.
Pre-warm caches and infrastructure
Since major live shopping events have known start times, proactive scaling avoids relying entirely on reactive autoscaling during the first, most dangerous seconds of a spike.
Build a “kill switch” for features under stress
Operators should be able to quickly disable non-essential features (e.g., certain chat effects, recommendations) during an ongoing incident, to shed load from the parts of the system that matter least.
16.2 Common Mistakes
This serializes unrelated purchases of different products through one bottleneck; locking should always be scoped to the individual product (or even sub-shards of a single hot product), never global.
Naively looping through a list of connected users’ sockets in application code to broadcast a chat message does not scale; a proper pub/sub fan-out mechanism is required from the start.
If a viewer starts checkout but closes the app before completing payment, without a TTL-based hold expiry (Section 5.2), that unit could be locked away from other buyers indefinitely.
If video is delayed by 8 seconds but chat/stock updates are near-instant, viewers may see chat reacting to a moment they haven’t watched yet, or see “sold out” before the creator even finishes describing the item on their delayed video feed — confusing UX that erodes trust. Keeping video and data-layer latencies reasonably aligned (or explicitly designing around the gap) matters.
Teams often stress-test successful purchases extensively but under-test what happens when payment fails after inventory reservation — exactly the path most likely to cause subtle inventory drift bugs in production.
Real-World Industry Examples
Live shopping is not a hypothetical exercise — it is a large and fast-growing part of e-commerce today. A few real platforms illustrate different points on the design spectrum we’ve discussed:
TikTok Shop
Built directly into TikTok’s short-video and live-streaming app, TikTok Shop turns the existing massive live audience into a real-time sales floor, blending the video plane and commerce plane into a single native experience rather than linking out to a separate storefront. It has become one of the primary drivers of live shopping growth in the US market since its 2023 launch, with reported year-over-year sales growth in some categories exceeding 100%.
Amazon Live
Amazon Live lets Amazon sellers and approved influencers broadcast product demonstrations directly within Amazon’s existing marketplace and checkout infrastructure. Because it reuses Amazon’s already-proven catalog, inventory, and payment systems rather than building commerce from scratch, its architecture emphasizes tight integration between the video layer and Amazon’s existing order pipeline over building an independent commerce stack. Industry reporting suggests the large majority of Amazon Live viewers take some action — clicking through or buying — during or immediately after a stream, underscoring how directly video engagement here converts into checkout load.
Live-auction native
Whatnot built its entire platform around live-auction-style selling for collectibles, trading cards, sneakers, and fashion — a format where “inventory” is often literally a single unique item, making the atomic reservation problem from Section 5 especially visible: exactly one bidder can win each item, live, on camera, with everyone watching the countdown at once.
Taobao Live
Taobao Live pioneered live commerce at massive scale in China years before most western platforms, with top streamers selling volumes of merchandise in a single broadcast that rival the revenue of entire retail chains over much longer periods. Its scale requirements are what pushed the industry toward exactly the kind of highly-sharded, cache-first inventory architecture described in this tutorial.
Brand-owned live commerce
Dedicated live-commerce SaaS platforms have also matured to serve brands directly on their own websites rather than through a third-party marketplace, prioritizing tight, low-friction integration between the video experience and existing e-commerce backends such as Shopify, so that catalog and inventory data sync natively rather than through custom integration work.
Every one of these platforms, despite different business models, converges on the same core technical shape — a fast video pipeline, an aggressively cached and atomically-safe inventory layer, and a real-time social layer — because the underlying constraints (compressed demand, synchronized viewers, zero-tolerance for overselling) are the same no matter who builds the platform.
FAQ
What happens if two viewers click “Buy” on the very last unit at the exact same millisecond?
Redis processes commands against a single key one at a time, even under massive concurrent load, because the atomic Lua script (Section 5.2) executes as one indivisible unit. Whichever request’s script execution happens to run first (a matter of nanoseconds, decided by the server, not by the client) succeeds; the other receives an immediate, honest “out of stock” response. There is no window where both could succeed.
Why not just use a database transaction with row-level locking instead of Redis?
A relational database can absolutely enforce this correctness with row-level locking (e.g., SELECT ... FOR UPDATE), but under extreme concurrency — thousands of simultaneous requests targeting the same row — lock contention and connection pool exhaustion become serious bottlenecks. Redis’s in-memory, single-threaded command execution handles this specific pattern (high-frequency atomic counter operations) with far higher throughput, which is why it’s used as the fast path, with the database serving as the durable backstop.
How do you prevent a bug or crash from leaving the Redis stock count permanently out of sync with the database?
A periodic reconciliation job compares Redis counters against the database’s authoritative ledger for recently active products, alerting or auto-correcting on drift. Because every reservation, confirmation, and release is also logged as an event (Section 15.3), any discrepancy can be traced back to its root cause using the event history, not just patched blindly.
How does the system handle a creator’s internet connection dropping mid-stream?
The Stream Ingest Service detects the dropped connection quickly via heartbeat timeouts. The player shows a “reconnecting” state to viewers rather than abruptly cutting the stream, and if the creator’s app is configured with a backup ingest endpoint or automatically retries, the stream resumes with minimal disruption. Any products that were actively selling remain purchasable in a paused state; the platform typically does not auto-cancel in-progress orders just because the video feed briefly drops.
Can viewers still buy a product after the live stream ends?
Yes — most platforms keep the stream’s replay (VOD) available afterward with the same shoppable product tags, letting viewers who missed the live moment still purchase, as long as stock remains, through the same Catalog and Inventory services, just without the live urgency and chat layer active.
How would you extend this design to support live auctions instead of fixed-price “buy now” purchases?
An auction adds a bidding sub-flow: instead of an immediate atomic decrement, each bid is validated against the current highest bid (again using an atomic Redis operation to avoid race conditions between simultaneous bids) and broadcast to viewers via the same chat/presence pub-sub mechanism. Only when the countdown ends does the Order Service create an order for the winning bidder, reusing the exact same reservation-then-confirm flow described in Section 5.
Summary and Key Takeaways
Designing a live shopping platform means designing three systems that must feel like one seamless experience to the viewer: a low-latency video pipeline, a rock-solid real-time inventory and checkout system, and a highly scalable social/chat layer. The single hardest and most important problem is preventing overselling under extreme, synchronized concurrency, solved here with atomic operations in an in-memory cache, backed by durable, optimistically-locked persistence in a relational database as a safety net.
The six ideas that hold this whole system together
- Not every part of the system needs the same consistency guarantee. Money and stock demand strong consistency; viewer counts and chat can be eventually consistent and approximate.
- Atomic operations, not application-level check-then-write, are what actually prevent race conditions under real concurrent load.
- Event-driven design (Kafka) decouples the fast, critical checkout path from slower, non-essential downstream work like shipping, notifications, and analytics.
- A CDN is non-negotiable for video at this scale — direct server-to-viewer delivery cannot support millions of simultaneous watchers.
- Because live events are scheduled, not organic, proactive pre-scaling beats purely reactive autoscaling for surviving the first critical seconds of a spike.
- Graceful degradation matters: a well-designed system keeps the core “watch and buy” experience alive even when secondary features (chat, recommendations, analytics) are under stress.
None of the individual pieces here are exotic — CDNs, Kafka, Redis, Kubernetes, and relational databases are all well-understood, widely used technologies. What makes live shopping a genuinely hard system design problem is the combination: video, commerce, and social interaction all converging on the same short, synchronized, high-stakes window of time, with real money and real inventory on the line, watched live by everyone at once.