Question presented to candidate: "When an order is saved, our order service also publishes an OrderPlaced event to Kafka for billing and email. Sometimes an order exists with no event, and once we sent an email for an order that was never saved. What is going on, how would you fix it with the outbox pattern, and how would you get rows out of the outbox: by polling, or with change data capture? Could we skip the outbox and just poll the orders table?"
What a strong answer should cover:
- The dual-write problem: a database write and a broker publish are two separate systems with no shared transaction, so a crash or error between them loses an event (commit first) or invents one (publish first)
- The transactional outbox: write the business row and an event row to an
outboxtable in one local transaction; a separate relay publishes outbox rows afterwards - The relay gives at-least-once delivery: a crash after publishing but before marking a row republishes it, so consumers deduplicate by event id (an inbox table or idempotent handlers)
- Two ways to drain the outbox: a polling publisher (
SELECT ... WHERE published = false, simple, adds query load and latency) or log-based CDC tailing the WAL (Debezium outbox event router; low latency, more infrastructure) - Why query-based polling of business tables (
updated_at > watermark) is not a substitute: it misses deletes, collapses intermediate updates, and skips rows from transactions that commit late - Operational details: ordering per aggregate (partition by aggregate id), outbox cleanup, multiple relays (
FOR UPDATE SKIP LOCKED), monitoring relay lag
Clarifying questions expected:
- What end-to-end delay from commit to event is acceptable: seconds, or tens of milliseconds?
- Do consumers need every intermediate change and deletes, or only the latest state?
- Is running Kafka Connect and Debezium acceptable operationally, or should this stay inside the application?
Code / implementation expected: Yes — sketch the outbox table, the single transaction and the relay loop; the example measures lost, phantom, duplicate and missed events for each approach.