Hard system-design search

How do you ingest product updates with CDC or application events?

The 30 second answer

I prefer change events over polling. CDC from the database or a transactional outbox gives at-least-once delivery into a bus, then a stream job joins product, price, and stock into one search document. Thin events that only carry an id force a re-fetch; fat events risk fan-out of huge payloads. Idempotent consumers, poison-event handling, and a scheduled backfill are mandatory. I would ask which fields must be near real time before sizing the pipeline.

Goal. Turn every relevant catalog change into an updated search document without dual-writing from every service.

Two common producers

  1. Application events / outbox. The service that commits the product write also writes an outbox row in the same transaction; a relay publishes to the bus. Pros: business-meaningful events. Cons: every writer must participate.
  2. CDC (logical replication). A platform tails Postgres (or similar) and emits row-level changes. Zalando has publicly described Fabric Event Streams: embedded Debezium micro-apps, CRDs for source/flow/sink, sinks to Nakadi event types or SQS, supporting outbox-style flows. That is a company-wide CDC platform; public posts do not claim it is wired directly into search, so in an interview phrase it as a pattern, not as a private fact.

Bus shape. Many orgs put Kafka underneath. Zalando's open Nakadi project is a REST abstraction over Kafka with event types, schemas, at-least-once delivery, and cursor commits — useful as a public example of "protect the cluster, enforce schema, authorize per event type." Weakness to mention: a bad event can stall a partition cursor unless DLQ / skip policies exist (Nakadi added auto-DLQ / unprocessable-event policies in 2023).

Join / enrichment. Price, stock, product attributes, and boost/sponsored metadata rarely arrive as one message. A stream processor (often Flink) keys by SKU and maintains join state. Zalando's Mar 2026 Flink post describes replacing chained Table API joins with a union + keyed process function and a single value state, cutting state roughly 240 GB → 56 GB for product-offer enrichment that feeds the catalog. Opinion: that post is about offer enrichment, not a full end-to-end search ingest diagram — say so if you cite it.

Thin vs fat events (Netflix contrast). Netflix Studio Search has described thin CDC/application events (id only), then re-fetching the full entity through GraphQL before Elasticsearch. Cost sits on source services. Keeping rich state in Flink moves cost to operator state size. Pick explicitly.

Hardening. At-least-once ⇒ idempotent upserts by document id / version. Poison messages need DLQ. Retain a full reindex / snapshot restore path for schema changes and disaster recovery.

Sources:

Sources (0)
See inline citations in the answer above.