Case study: Notification system
Design a notification pipeline: a durable queue between fast producers and flaky providers, retries with breakers, priorities, and back-pressure when the queue fills.
30 min9 steps
By the end you can
- Size sender concurrency, queue partitions and outage backlog with Little's law
- Design at-least-once delivery with idempotency keys, collapse keys and a dead-letter queue
- Protect providers and the pipeline with backoff, circuit breakers, bulkheads and priorities
- Predict how provider errors, outages and slowdowns surface to callers
Requirements
Features
- Push/SMS/email; preferences, quiet hours, caps, status, retries
Targets
- Accept <50 ms independent of providers; 2FA seconds; marketing waits
Delivery
- At-least-once; suppress duplicates where channel permits
Estimates
Volume
11.6k/s
1B/day; campaigns peak 10x
10-minute backlog
7M messages
11.6k * 600; 7 GB at 1 KB
Concurrency
460 calls
11.6k/s * 40 ms
Ordered partitions
At least 460
One in-flight message/partition, or unordered consumption
API
Accept
POST /v1/notifications; user, template, params, priority + Idempotency-Key
Result
201 queued; 200 duplicate; 429 quota
Inspect
GET /v1/notifications/{id}; status + attempts
Preferences
PUT /v1/users/{id}/preferences; channels, quiet hours, opt-outs
Data model
Notifications
ID, user, template, channel, status, attempts, timestamps; 6 TB/30 days
Preferences
Cache; evaluate at send time
Devices
Push tokens/user; delete provider-reported invalid tokens
High-level design
Order a sender's handling of one message.
- 1Check preferences, opt-outs and quiet hours
- 2Call the provider through the breaker, retrying once with backoff
- 3Acknowledge the message only after the provider accepted it
- 4Take a message from the queue (it becomes invisible to other senders)
Deep dive: retries, breakers and priorities
Transient
- Timeout/5xx/429: jittered backoff; provider token bucket
Permanent
- Remove invalid tokens; never retry
Isolation
- Breakers, bulkheads, separate priority pools
Collapse
- APNs/FCM replace pending messages; no universal exactly-once delivery
Alternatives
Synchronous provider
- Fewer parts; callers inherit latency/outages
Channel + priority queues
- Independent scaling/rate limits; more queues to operate
Failure scenarios you can run
Inject
50% failures; health checks pass
Observe
Breaker opens; redeliver later
Recover
API accepts; watch redelivery count
Inject
Provider down
Observe
1,000-message queue fills
Recover
Publish returns 503; size for outage horizon
Inject
Provider adds 250 ms
Observe
128 senders: 440/s vs 1k/s arriving
Recover
Queue fills; API returns 503
Wrap up
Buffer
Disk-backed queue sized for hours
Send
Preferences now; jitter, breakers, bulkheads, quotas, priorities, DLQ
Watch
Queue depth/age by priority; provider errors; DLQ growth
Then score each stage from 1 to 4.
Answer this step to continue.