Design a Reminder Alert System
A reminder alert system is a timing engine that stores future reminders and fires each one at its due moment. Firing means sending the reminder to its owner through the chosen channel.
The ask
A reminder names an owner, a due time, a short text and a channel. Channels are push, email and text message.
The system fires each reminder at its due time. A late reminder is worse than a slightly early one, and a lost reminder is worst.
Owners can cancel a reminder or snooze it. Snoozing moves the due time once.
Recurring reminders fire again on their rule. Each firing is a separate delivery.
Estimate the scale
1,000,000,000 reminders are stored.
100,000,000 reminders fire a day.
100,000,000 / 86,400 = 1,157 reminders per second.
A day holds 1,440 minute buckets.
100,000,000 / 1,440 = about 69,444 reminders in an average bucket.
API and data
The create operation takes the owner, the due time, the text, the channel and any repeat rule.
Cancel and snooze operations name one reminder. A snooze carries the new due time.
A reminder record holds its due time, its state and its delivery attempts. Due time is the main index.
A fired reminder keeps a delivery record. The record shows which channel carried it and when.
High-level design
A store keeps reminders indexed by due time. A scheduler reads due reminders ahead of their minute.
Due reminders go onto a queue. Delivery workers send them through the channel services.
Calendar events, task apps and health apps all write reminders through the same operations. The engine stays shared.
Deep dive: minute buckets
Reminders are grouped into buckets by due minute. One bucket holds every reminder due in that minute.
The scheduler loads the next minute's bucket before that minute starts. Firing then runs from memory, ordered by second.
One scheduler holds the loading role under a lease. A second scheduler watches and takes over if the first stops.
A loaded bucket survives a scheduler crash in the store. The takeover scheduler reloads the current bucket and refires only unsent reminders.
Popular minutes, such as the top of an hour, hold far more than the average bucket. Buckets are split by owner range when one grows too large.
Delivery and retries
Each reminder carries one delivery identity. A retry reuses that identity, so a channel can drop a duplicate send.
A failed send is retried with growing gaps. A reminder that still fails is marked failed, and the owner sees that state.
A device that is offline gets its push on reconnect, inside a freshness limit. A stale reminder is marked expired instead of firing late.
Recurring reminders and snoozing
When a recurring reminder fires, the next occurrence is written at once. The series never depends on the delivery result.
A snooze writes a fresh one-off reminder and marks the original as snoozed. The original firing stays in the record.
Cancelling a series stops future occurrences. Past firings stay recorded.
Evaluation and trade-offs
A reminder alert system is a clock with a queue behind it. Buckets keep the clock cheap, and identities keep retries safe.
Minute buckets trade a little timing grain for steady load. In-memory ordering restores second-level accuracy inside the minute.
Loading ahead hides store failures at firing time. The lease keeps one loader, so buckets are never loaded twice.
Expiry rules keep late reminders honest. Firing a stale reminder on reconnect would trade trust for noise.
Reminder scale
| Item | Figure |
|---|---|
| Reminders stored | 1,000,000,000 |
| Reminders firing a day | 100,000,000 |
| Firing rate | 1,157 per second |
| Minute buckets a day | 1,440 |
| Reminders in an average bucket | 69,444 |
Exercise
Why are reminders grouped into minute buckets?