0 XP · 0 scenes

Event-driven architecture

Architecture Patterns · lesson 4 of 7 · about 28 min

Event-driven architecture

Services that react to events instead of being called: what an event should carry, who runs a multi-step process, why users sometimes read stale state right after writing, how skewed keys create hot partitions, how to follow one event through the system, and what a single poison event does to a partition. Tap any for a plain-language card.

0 / 9 sections done
  1. 1What an event carries4 min
  2. 2Who runs the workflow5 min
  3. 3Eventual consistency4 min
  4. 4Hot partitions4 min
  5. 5Following an event3 min
  6. 6Which design?2 min
  7. 7Who lives by this1 min
  8. 8Interview angle2 min
  9. 9Break It: the poison event3 min

Words in this lesson10Tap one for its meaning.

Events as the backbone

“OrderPlaced: id 42.” Is that enough, or do eight consumers now all call back asking for the order?

Neither is right everywhere. Notifications suit rare, large or sensitive data; state transfer suits data many consumers want and tolerate being slightly old. A third form, event sourcing, makes the events themselves the record of truth (see the CQRS lesson). Compare the load of the first two for your numbers.

With 8 consumers, notifications create 8,000 callbacks a second back to the publisher; state transfer creates none.

  • Callbacks to the publisher: notification
    8,000 requests/s
  • Callbacks: state transfer
    none
Data moved, notification + callbacks41.8 MB/s
Data moved, state transfer41.6 MB/s
Extra load on the publisher8,000 req/s vs 0

1,000 events a second to 8 consumers: notifications make 8,000 callback requests a second to the publisher (41.8 MB/s in all); state-carrying events make none (41.6 MB/s). The price of state transfer is that each consumer holds its own copy, which can be a little out of date.

Notification: small event + one callback per consumer. State transfer: the event carries the data. The two move similar bytes; the difference is the callback load and the runtime dependency on the publisher.

Who runs the workflow

An order passes through six services. Nobody can say where it is stuck.

Choreography is elegant for short, simple reactions, and becomes opaque as flows grow: the process exists nowhere in the code, so answering “where is order 42?” means reading the logs of each service in turn. Orchestration costs a central component and more messages, and makes the flow visible, testable and recoverable in one place. A good rule: choreograph simple fan-out notifications; orchestrate multi-step business processes with failure handling.

Make the process 8 steps with a failure at step 6: choreography needs six services' traces to find it, orchestration one status page.

  • Messages: choreography
    6
  • Messages: orchestration
    12
  • Services to inspect to find the stuck step: choreography
    4
  • Services to inspect: orchestration
    1
Services that know the whole flow (choreography)none: it is implicit
Services the coordinator must know (orchestration)6
Places to look when step 4 hangs4 vs 1

A 6-step process stuck at step 4: with choreography an engineer follows the event trail through 4 services; with orchestration the coordinator's state names the stuck step at once. Orchestration sends 12 messages instead of 6 and adds a central component, the price of visibility and control.

Counts of messages and inspection points, not times. In practice choreographed flows are made traceable with correlation ids.

Eventual consistency, felt by users

The user clicks Save, the page reloads, and their change isn't there. They click Save again.

Whether it happens is a race between the read model's lag and how soon the user looks again. Remedies, from simplest: return the new state in the write's own response and render it optimistically without re-reading; route the next reads to the write side for a short window; pass a version token so the reader can wait until the read model reaches it; or show an honest “saving…” state. Set the lag and the user's typical reload time.

A read model that is 500 ms behind shows stale data on about one reload in eight at a 1.5 s reload. At 3 s of lag it is most of the time.

  • lag 50 ms
    0.0% of reloads stale
  • lag 200 ms
    1.2% of reloads stale
  • lag 500 ms
    11.8% of reloads stale
  • lag 1 s
    33.8% of reloads stale
  • lag 3 s
    77.6% of reloads stale
Reloads that show stale data11.7%
Per 100,000 saves11,723

With a read-model lag of about 500 ms and users reloading after about 1.5 s, 11.7% of reloads land before the update arrives. Rare enough to tolerate, but never zero.

30,000 seeded saves: lag and reload delay are both log-normal around the medians; a reload is stale when it happens before the lag ends.

Ordering has a price

Events for one customer must be processed in order, so they share a partition. One customer is a thousand times busier than the rest.

The system is as fast as its busiest partition, not its average one. Adding more consumers does not help; adding partitions only helps if the hot key moves, and it cannot split, because splitting would break its ordering. Remedies: choose a key that spreads load (and only as fine-grained as the ordering you really need), add a random suffix to split a hot key when order across the pieces does not matter, and handle the largest tenants in dedicated partitions. Set the skew of your keys.

With Zipf-skewed keys over 16 partitions, the busiest partition carries about 15% of the traffic against an ideal 6%, so the consumers are used at only about 40% of their capacity.

  • busiest partition #1
    15.1% of events
  • busiest partition #2
    13.0% of events
  • busiest partition #3
    9.3% of events
  • busiest partition #4
    8.3% of events
  • busiest partition #5
    6.5% of events
  • busiest partition #6
    6.4% of events
  • busiest partition #7
    5.8% of events
  • busiest partition #8
    5.0% of events
Ideal share per partition6.3%
Busiest partition's share15.1%
Effective use of the consumers41%

With 5,000 keys of skew 1.0 over 16 partitions, the busiest partition carries 15.1% of events against an ideal 6.3%, so the whole pipeline runs at about 41% of the throughput its 16 consumers could deliver.

Keys have Zipf popularity and are hashed at random to partitions; efficiency = average partition load ÷ busiest partition load.

Debuggability

The customer says the confirmation never came. Which of three thousand events was theirs?

Without them an engineer greps by timestamps and customer ids across services and guesses. The cost of adding the ids is trivial at the start and enormous to retrofit across forty services. Make correlation ids part of the event envelope standard from day one, propagate them in every message header, and log them in every line. Set the shape of the flow.

With fan-out of 3 over 8 hops there are almost 3,300 events per original. With a correlation id you read 8; without, you search them all.

  • Events caused by one original
    3,280
  • Events you must read to follow the path
    3,280
Events caused by one original3,280
Events to inspect3,280
At 2 seconds each1.8 h

One original request caused 3,280 events over 8 hops with a fan-out of 3. Without a correlation id, finding the one chain that failed means sifting through them: about 1.8 hours at 2 seconds each.

Events = (fan^hops − 1) ÷ (fan − 1), a geometric series. Inspection assumes 2 seconds per event; with an id you read only the path.

Which design?

Six situations, four designs.

Notifications keep events small and consumers coupled by callbacks; state-carrying events decouple at the cost of copies; choreography is simple reactions; orchestration is a visible multi-step process. Each answer is worth 20 XP.

Ask: how big is the data, how many consumers, and does the process need compensation and visibility?

Question 1 / 6

A sensitive medical record changed; interested systems should know but must fetch it with their own access checks.

Tip: press 1 to 4 to answer, Enter for the next one.

Who lives by this

From Fowler's patterns to Temporal.

Four published sources on event-driven design.

Naming the patternsMartin Fowler (2017)

'What do you mean by Event-Driven?'

Fowler separated four things often called event-driven: event notification, event-carried state transfer, event sourcing and CQRS, pointing out that people discussing 'events' often mean different ones and that each has distinct trade-offs.

Orchestrated workflowsTemporal and Uber Cadence

durable workflow engines

Cadence, created at Uber, and its successor Temporal let developers write multi-step business processes as ordinary code whose progress is persisted so it survives crashes, with retries and timers handled by the engine: orchestration made durable.

Choreography at NetflixNetflix Conductor

a workflow orchestration engine

Netflix open-sourced Conductor, an orchestration engine that coordinates workflows across microservices from a central definition, after finding that purely event-driven choreography made processes hard to follow and manage as they grew.

Tracing the pathOpenTelemetry trace context

correlation across services

The W3C Trace Context standard defines the traceparent header that carries a trace id from service to service, and OpenTelemetry propagates it through HTTP calls and message headers so that the events caused by one request can be joined into a single trace.

Interview angle

What interviewers actually ask.

Six questions, each worth 20 XP. Event-driven answers are judged on whether you know the downsides: stale reads, ordering and debugging.

Name the kind of event, who coordinates, and how you debug and recover.

Question 1 / 6

What is the drawback of a tiny event notification?

Tip: press 1 to 3 to answer, Enter for the next one.

Break it

One malformed event at offset 4,711,203. The consumer fails on it, retries, fails, retries. Everything behind it waits.

The cure is a policy for failing events: retry a bounded number of times with backoff (many failures are transient), and then move the event to a with its error so a person can inspect and replay it, letting the partition continue. Alert on DLQ growth and on per-partition lag. The cost is losing strict order for that one event, which is almost always better than stopping everything.

With retry-forever, 500 events a second pile up for as long as the problem is unnoticed: 30 minutes is 900,000 delayed events. With 5 tries and a dead-letter queue the stall is 10 seconds.

Partition stalled for30 min
Events delayed behind it900,000

The consumer retries one bad event until someone notices after 30 minutes: the partition is stalled for 30 min and 900,000 events wait behind it, none of them at fault.

Stall = attempts × backoff (dead-letter) or the time until someone notices (retry forever). Events delayed = arrival rate × stall.

Where to go next