sehaj/learn
System design/12 Realtime, feeds and location

12.08 · concept · 10 min

Notification fan-out

One event, a million phones: expanding recipients in batches, applying each user's preferences and limits, routing to push, in-app and email, respecting providers' rules, collapsing updates that replace each other, and never sending the same notification twice.

The problem: a creator with a million followers posts

A creator on Snip Bio posts a new link. Their 1.2 million followers who have notifications on should hear about it: a push notification on their phone, an update in the app if it's open, maybe a line in tomorrow's email digest. It should arrive within a minute or two, respect each person's settings and time zone, and never arrive twice.

Doing this inside the "publish link" request is impossible. It's a pipeline.

The pipeline

From one post to a million phones
  1. 01

    Event

    link.published is written to the outbox and published (07.06).

  2. 02

    Expand

    A worker pages through the creator's followers, 1,000 at a time, emitting one job per batch.

  3. 03

    Decide

    For each recipient: preferences, quiet hours, rate limits, deduplication. Most are filtered or deferred here.

  4. 04

    Route

    Per channel: in-app via the user's connection, push via the providers, email into tomorrow's digest.

  5. 05

    Deliver

    Channel workers send to Apple and Google push services within their limits, retrying and recording results.

Expand in batches. A million recipients is a million small jobs. Paging through followers in batches of 1,000 creates 1,200 jobs, which spread across workers and can't overwhelm anything at once.

Decide per recipient. The most important step is often deciding not to send:

  • Preferences: has this person muted this creator, or turned off "new link" notifications?
  • Quiet hours: it's 03:00 where they are; hold it until morning, or move it to the digest.
  • Rate limits: they follow 400 creators; ten posts in an hour should become one "10 new links from creators you follow", not ten buzzes.
  • Deduplication: a key per (user, event), so a retried batch never notifies anyone twice (07.03 Delivery guarantees and idempotent consumers).

The channels

  • In-app: if the user's app is connected (12.01 Millions of WebSocket connections), push the update over their connection. Cheap and instant, and it can replace a phone notification entirely.
  • Mobile push: the app's servers send to Apple's and Google's push services, which deliver to the device. They have their own rules and limits, so a channel worker paces sends, retries with backoff, and removes device tokens the service reports as invalid.
  • Email: batch into digests; individual emails for each event are rarely welcome.

Collapse what replaces itself

Some notifications supersede earlier ones: "your link has 1,000 clicks", then "2,000 clicks". The push services support this directly. With Google's Firebase Cloud Messaging, a collapsible message carries a collapse key, and a newer message with the same key replaces one not yet delivered: a phone that was offline gets only the latest count. FCM stores up to four different collapse keys per device at a time. Non-collapsible messages are each delivered individually.

Spread the spike

A million notifications released at once also means a million people opening the app at once. Stagger delivery over a couple of minutes, send to recently active users first (they're the likeliest to open it), and give the "notification opened" traffic path enough headroom.

Seen in production

Google

Docs

Firebase Cloud Messaging

A collapsible message may be replaced by a newer message if it hasn't been delivered yet, while each non-collapsible message is delivered individually. FCM can store four different collapsible messages per device, each with a different collapse key, and allows at most four collapse keys per registration token at a time. By default the collapse key is the app's package name, notification messages are always collapsible, delivery order isn't guaranteed, and the maximum payload is 4,096 bytes.

What we foundMessages that supersede each other share a collapse key, so an offline device receives only the newest.

Collapsible and non-collapsible messages (Firebase documentation)firebase.google.com ↗
Predict first

A batch job sending 1,000 push notifications crashes after sending 600, and the batch is retried. What prevents 600 people getting the notification twice?

Commit to an answer before you read on.

Recap

Interview questions

SeniorfreqMust-knowDesign

Design a notification system that can notify millions of followers when someone posts.

Report a mistakeEdit this page