Scenario: you wanna delivery users Notifications (e.g. email) at certain intervals. The system is run periodically and we need to work out whether to sent or not. Simples, right? Um…
We’re in python and using croniter
package. We reuse the concepts of cron
, which is the obvious parallel in linux. Let’s think of cron
as just an interval maker:
| <- window -> | <- window -> | <- window -> |
^ ^ ^ ^
action action ... ...
# cron format is:
minute hour day-of-month month day-of-week
# Some examples:
Once a minute:
* * * * *
Once an hour (at 1 minute past the hour)
1 * * * *
Once a day (at 01h01 in the morning)
1 1 * * *
Once a month (at 00h00 on first day)
0 0 1 * *
Note all of these are regular instants in time. We define a ‘window’ as shown above. We call “now” the time the script is run. We define a sent_in_window()
function which determines if a Notification has been (previously) sent in that the current window.
Let’s understand with a run through:
Initially, we have no sends:
Schedule: | <- window -> | <- window -> | <- window -> |
^
now
sent_in_window() == False
For the first run, either we (1) send immediately or (2) (better) send next interval. So if is_first()
then use created_at
date of Notifier as the ‘first’ point
Schedule: | <- window -> | <- window -> | <- window -> |
^ ^
created_at now
Since sent_in_window()
= True, we don’t send. Ok, that’s good.
Now later on:
Schedule: | <- window -> | <- window -> | <- window -> |
^ ^
created_at now
sent_in_window()==False => do send
Now even later on:
Schedule: | <- window -> | <- window -> | <- window -> |
^ ^ ^
created_at sent now
sent_in_window()==True => don't send
And finally later on:
Schedule: | <- window -> | <- window -> | <- window -> |
^ ^ ^
created_at sent now
sent_in_window()==False => do send
Edge cases and thoughts
We want to run pretty close to the scheduled time, so we should run often.
If we run very late in the interval, two notifications could be send almost simultaneously, one before and one after the instant requested. (Consider disallowing sends in last (arbitarily) half of window).
Another issue with late sends is that we probably shouldn’t use now()
and a duration (e.g. 30d) to define the window
. Instead, we should pass the required window (start,end) to the Notification
and construct it based on that. For example, send a monthly email with charts on 05 Feb should display 01 Jan to 31 Jan (not 6 Jan to 5 Feb).
If pending, and we don’t send (e.g. later in the pipeline, something filters it out), that’s ok. The Notification
will not be created and the next run will try to send it.
If we completely miss a window that’s ok too – we just won’t send a Notification in that window
.
Implementation
.