Case study · Retail streaming

Replacing nightly stock syncs with a live inventory stream

Stock moved all day in stores, warehouses and online, but channels learned about it once a night. Overselling was the predictable result. The goal was to stream every committed stock and order change from the operational databases into projections that every channel could read in near real time.

Case studyRetail · Stores, web and marketplacesSumanta Mukhopadhyay8 min read
#streaming#data#architecture#platform
CASE STUDY · CASE STUDY · RETAIL STREAMING28 Oct 2026 · 01:24 GST
At a glance
SectorRetail · Stores, web and marketplaces
My rolePrincipal architect
EngagementArchitecture, build and hand-off
ScopeCDC, event streaming, inventory projections, channel integration
What changed
  • Channels read stock from a continuously updated view instead of a nightly snapshot
  • Order and stock events available to any new consumer without touching the source systems
  • Replayable history, so projections can be rebuilt or corrected from the log
  • Clear ownership of event contracts between the teams that produce and consume them
DebeziumApache KafkaKafka ConnectTransactional outboxStream processingCompacted topicsRedisSchema registryOpenTelemetry

Anonymised composite. Client names, identifying details and confidential figures are withheld; public facts are cited in the references.

The situation

A retailer sold through physical stores, its own website and several marketplaces. Stock levels lived in a warehouse system and store systems, orders in an order management database, and each channel received a stock file once a night. During the day, the same last unit could be sold in a store, online and on a marketplace before anyone knew.

The workarounds made things worse: large safety buffers that hid sellable stock, manual cancellations, and ad hoc point-to-point integrations that each team had built to get fresher data for its own channel.

What had to be true

  • Every committed change is captured, without polling or adding load to operational databases.
  • No dual writes: an order and its event either both happen or neither does.
  • Channels read a projection, not the source systems.
  • History is replayable, so a bug in a projection can be fixed and rebuilt.
  • Contracts are explicit, so source teams can change their schemas without breaking consumers.

Architecture

Log-based change data capture reads committed row-level changes from the database's transaction log, so applications consume changes rather than querying the source1. That became the backbone, with a stream processing layer building inventory views for each consumer.

Sources
Order managementrelational DBWarehouse systemstock movementsStore systemssales, returns
Capture
Outbox tablessame transactionDebeziumKafka ConnectSchema registryversioned contracts
Stream
Kafka topicskeyed by SKU and locationCompacted state topicslatest per keyDead-letter topicswith reasons
Project
Available-to-sellstream processorReservationspending ordersLocation rollupsstore, region, online
Serve
Redis read modellow-latency lookupsWeb and applive stockMarketplace feedsincrementalAnalyticssame events
Layered architecture: capture once from the log, project many times for each consumer.

The data flow, end to end

  1. CommitOrder or stock change and its outbox event written in one database transaction
  2. CaptureDebezium reads the log and routes outbox events to domain topics
  3. ValidateSchema checked against the registry; malformed events to dead-letter
  4. ProjectStream processor updates available-to-sell per SKU and location
  5. ServeRead model updated; channels query it or receive incremental updates
  6. ReconcilePeriodic comparison with source counts; drift raised, not hidden
Every step is idempotent and keyed, so replays and retries do not double-count stock.

More on the capture side in change data capture and event streams, and on duplicate-safe consumers in idempotent order pipelines.

The hard part: events that mean something

Streaming raw table changes was quick to demonstrate and the wrong long-term contract. Consumers ended up decoding internal columns and status codes, and any schema change in the source broke them. The transactional outbox pattern addresses this: the service writes a domain event to an outbox table in the same transaction as the business change, and a relay publishes it3. Debezium's outbox event router does that relay from the log, routing events to topics by aggregate type and keying them by aggregate identifier2.

  • Events were designed with the consuming teams: "stock adjusted", "order allocated", "reservation released", not "row updated".
  • Keys were chosen for ordering: everything about one SKU at one location lands in one partition, in order.
  • Contracts went into the schema registry with compatibility rules, following a data contracts approach.

For current state, compacted topics keep the latest record per key and support rebuilding state after a failure4, which let new projections bootstrap without a bulk extract from the source.

CDC moves data quickly; it does not decide what the data means. Design the events with the teams who will read them.

How it ran

Source teams
  1. Add outbox writes
  2. Own event schemas
  3. Keep compatibility
Platform
  1. Run Connect and Kafka
  2. Operate the registry
  3. Monitor lag and drift
Channel teams
  1. Read projections
  2. Report mismatches
  3. Retire old file feeds
Producers own their events, the platform owns the pipes, consumers own their projections.
Phase 1Map
  • Stock and order flows
  • Where overselling occurs
  • Event catalogue draft
Phase 2Thin slice
  • One source with outbox
  • One projection
  • One channel in shadow
Phase 3Extend
  • Remaining sources
  • Reservations logic
  • Reconciliation jobs
Phase 4Switch and hand off
  • Channels cut over
  • Nightly files retired
  • Runbooks and on-call
Each channel ran in shadow against the nightly file before cutting over.

Operationally, the signals that mattered were connector health, consumer lag per projection and the reconciliation drift report. Those went on one dashboard owned by the platform team, with alerts tied to what a channel would notice (stale stock) rather than to every broker metric. Traces carried the event identifier from commit to read model, so a disputed stock figure could be followed back to the change that produced it.

Key decisions and trade-offs

DecisionAlternativeWhy
Log-based CDCPolling or nightly extractsCaptures every committed change with little source load
Transactional outboxRaw table change eventsStable domain contracts; no dual writes
Key by SKU and locationKey by orderOrdered updates for the thing being counted
Separate read modelChannels query sourcesProtects operational systems; fast reads
Reconciliation jobsTrust the streamDrift is surfaced and explained, not discovered by customers

What I'd do again, and differently

Again: a thin slice end to end before scaling, shadow running against the old feed, and reconciliation from the start. Differently: agree the reservation and allocation rules with operations earlier. Live data exposed business rules that had never been written down, and those took longer to settle than the streaming platform.

If you are planning a similar move off batch, the fractional CTO page describes how I work with teams on it, or analyse your challenge and bring the result to a call.

References
  1. 1
  2. 2
  3. 3
    Pattern: Transactional outboxmicroservices.io (Chris Richardson)
  4. 4
    Kafka design: log compactionApache Software Foundation
FAQ

Common questions

What is change data capture?

Change data capture reads committed changes from a database's transaction log and publishes them as events, so other systems can react without polling or nightly extracts.

Should you stream raw table changes or domain events?

Raw table changes are quick to start but couple consumers to the source schema. The transactional outbox pattern publishes deliberate domain events in the same transaction as the change, which is safer as the number of consumers grows.

Does real-time inventory remove overselling entirely?

No. It narrows the window in which channels act on stale stock. Reservations, safety buffers and clear rules for concurrent orders are still needed.

Keep reading

Related

Open to advisory, fractional CTO & select builds

Let's talk about what you're building.

The fastest route is a free 30-minute call. Prefer to write? Send a message and I'll reply personally within two business days.

Start a conversation

How would you like to reach me?

Book a 30-min callFastest. Pick any open slot on my calendar.→ Write in GmailOpens a pre-filled draft in your browser.→ Use my email appOutlook, Apple Mail or your default client.→

I reply personally within two business days. Time zones from the Americas and Europe to the Middle East and Asia-Pacific.