advanced 2 min answer

Design push and background work for a chat application delivering to hundreds of millions of devices.

pushnotificationsfanoutbatterydiscord
Show the full answer Hide the answer

The delivery reality

Push is best-effort: not guaranteed, not ordered, potentially delayed, and subject to platform throttling that increases as a user interacts less with the app. Payload size is small.

So push is a hint to fetch, not a carrier of state. A design that treats a notification as the delivery mechanism for content will lose messages and display stale ones.

The fanout problem

A message to a large channel means a notification per subscribed device, and the naive implementation generates that fanout synchronously on the message path — coupling message delivery latency to notification volume.

  • Separate the paths. Message delivery to connected clients is real-time; notification fanout is asynchronous and may lag without affecting chat.
  • Suppress notifications for connected clients, since a user with the app open does not need one and this removes a large share of the volume.
  • Aggregate rather than send per message. A busy channel must produce a summary, not fifty notifications — this is both a user experience requirement and the main volume control.
  • Respect per-user and per-channel preferences at fanout time, not on the device, so undeliverable notifications are never generated.

Token and background handling

  • Token lifecycle management: tokens rotate, expire and become invalid, and a system that does not prune them wastes a growing share of its fanout on dead devices.
  • Background fetch is limited and unreliable on both platforms — treat it as an optimisation, never as a correctness mechanism.
  • Coalesce background work, since radio wake-ups dominate battery cost and several small syncs cost far more than one combined one.

The failure mode to design against

A backlog burst on reconnection. A device offline for hours reconnects and the server sends everything at once. Rate-limit the drain, prefer a summary over a replay, and let the client fetch history on demand rather than pushing it.