Scheduling notifications reminders for users

algorithmscronqueueruby-on-railsscheduling

I am trying to design the best way to send reminders to users for events they are registered for:

  • reminders should be sent 72, 48 and 24 hours before the event
  • reminders cannot be sent twice (so user A registered to an event B should receive at most 3 notifications)
  • if users register after one of the three intervals they won't receive the previous obviously
  • event times cannot be changed after the first registration is in

This is a Rails app with Sidekiq and Redis (right now we just send a notification upon registration which is not scheduled).

I see at least two ways to solve this:

A. Schedule jobs at registration time

  • if the registration time is earlier than 72 hours from the event then schedule a notification (event date – 72 hours) and queue it
  • if the registration time is earlier than 48 hours from the event then schedule a notification (event date – 48 hours) and queue it
  • if the registration time is earlier than 24 hours from the event then schedule a notification (event date – 24 hours) and queue it

This should cover the following scenarios:

  • if the user removes his appointment in any of the time frames they won't receive the other notifications (the queued jobs will simply fail)
  • a user cannot receive twice the same notification because there are at most 3 jobs scheduled
  • if the event is completely deleted from the DB and so are its registrations those queued notifications will just fail

The drawbacks:

  • probably many dead jobs in the queue if many users unregister
  • it could happen that a user is sent a notification twice (if they register, then unregister and then register again) but this could be solved by using something like https://github.com/mhenrixon/sidekiq-unique-jobs that allows only one type of job in the queue matching based on the arguments

B. Polling with a task

Basically every X minutes a background task (can be done with Unix cron or sidekiq's cron extensions) checks the "events" table, checks if it's 72, 48 or 24 hours from the event and queues an immediate job per each registration.

This has a few drawbacks:

  • a user might miss a notification if the time check algorithm is not precise enough (if they register inside the polling window they might get skipped)
  • requires a mechanism to flag that a notification for a particular user has been sent. This can also be solved by relying on the library that forbids jobs with the same arguments in the queue

Conclusion

I am leaning towards the "scheduling in advance" approach. There's no external cron configuration, I do not need to stress the DB every X minutes with queries that might become heavy if there are hundreds of events with hundreds of users registered and I can monitor what's going on from the Sidekiq web dashboard.

Polling seems to be the easiest because of the fire and forget nature but it can't be tricky to optimize down the line. A huge advantage of polling is that it does not require a Redis configured with persistence. If the machine (or Redis) is restarted the next cron iteration will mostly just pickup where it left off.

The scheduling solution requires a Redis with persistence configured because if the machine is restarted it will lose all the scheduled jobs but that might be a problem in general so that's just a general requirement.

Any ideas? Am I missing something?

Best Answer

The ideal design would probably be A, with the simple modification that you delete any related notifications from the queue when a user de-registers. This modification would solve both of the listed drawbacks.

However, either design would be acceptable. It would be fine to go with B if practical concerns make this easier to implement.