intermediate 3 min answer

A courier app relies on a data-only push to trigger a background sync. The push provider's dashboard reports 99.3% delivered. Product reports that about 6% of couriers see stale job lists for hours, and support notices these users are disproportionately on low battery or older devices. Where do you look, in what order, and what is the misleading signal?

pushbackground-workdozebatterymobile
Show the full answer Hide the answer

The first three things I would look at

  1. What "delivered" means on that dashboard. For both major platforms it means accepted by the operating system's push service, not that your process ran. That is the misleading signal, and it is why the 99.3% and the 6% are both true.
  2. The distribution of time between push acceptance and the app's sync request, bucketed by device state. If the median is seconds and the 95th percentile is hours, you are looking at deferral, not loss.
  3. The push priority and rate you are actually sending. Apple's documentation is explicit that background notifications are low priority, are not guaranteed, and that you should not send more than two or three per hour. On Android, a normal-priority message to a device in Doze is held until a maintenance window; only a high-priority message wakes it.

The diagnosis

The operating system is doing exactly what it documents: your background wake-up is a request, not an instruction. Low battery, Battery Saver or Low Power Mode, a device that has not been unlocked recently, and an app the user rarely opens all push you into a more restrictive bucket. Android's App Standby buckets make this explicit: an app in the "rare" bucket gets its jobs and its normal-priority messages deferred far longer than one in the active bucket. The affected 6% are not a random sample; they are the population the platform has decided to protect.

The second-order effect is self-inflicted. If you send a silent push per job event, a busy courier generates dozens an hour, and exceeding the platform's guidance is itself a reason to be throttled, so the couriers with the most work get the least reliable sync.

The fix

  • Never make the push the transport. Send a small payload saying "state changed as of version N" and let the client fetch. Better still, make the client's own foreground refresh authoritative and treat the push as an accelerator.
  • For anything a courier must see, use a user-visible notification with the correct interruption level. Those are delivered under rules you can reason about; background wake-ups are not. The design question is whether the update is worth interrupting someone, and if it is not, it is not worth fighting the scheduler for.
  • Coalesce. One push per device per minute carrying a version number, not one per event.
  • Add a foreground-service or long-lived socket path for on-shift devices where the platform permits it for an active delivery task, and fall back to push off-shift.
  • Make staleness visible in the UI. "Updated 4 minutes ago" turns an invisible failure into a user action, and it is the cheapest change on this list.

The alert that would have caught it earlier

Not delivery rate. Age of the newest job the client has actually seen, reported by the client, at the 95th percentile, segmented by platform and power state. Provider-side metrics cannot see this by construction, which is the general lesson: when a dependency reports success at its own boundary, you need a signal measured past that boundary.

When this is the wrong answer

If the app is only used in the foreground during a shift, all of this is wasted effort: a socket or a 30-second poll while the screen is on costs less to build and behaves predictably, because the operating system does not throttle a foreground app. Choose the push path only for state changes that must reach a backgrounded device, and accept that its worst case is measured in hours rather than seconds. The platforms document this as best-effort delivery and have since Doze arrived in 2015; a design that treats it as a guarantee fails on the devices whose owners are most careful with their battery.

Common weak answers

  • "Switch push providers." The throttling is done by the operating system after the provider hands the message over, so a new provider changes the dashboard and not the outcome.
  • "Send more silent pushes so one gets through." Exceeding the documented rate is itself a throttling trigger, so this costs delivery on the busiest devices.
  • "Ask users to disable battery optimisation." A support burden that a minority will complete, and on recent platform versions it is not fully within the user's gift.