I have to send out thousands of reminders, any way to avoid a ticker every minute?

Issue

I have a struct like:

type Notifications struct {
  Id int
  Start *time.Time
}

notifications := db.GetNotifications()

So now I need to send out these notifications whenever the time matches the current time.

1  2018-11-07 09:05:00
2  2018-11-07 09:05:00
3  2018-11-07 09:15:00
..

The simplest way for me to do this is with a ticker:

ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()

for {
    <-ticker.C
    alerts := []Notification
    for _, n := range notifications {
      if n.Start == // same year, month, day, hour and minute {
        alerts = append(alerts, n) 
      }
    }

    sendNotifications(alerts)
    // TODO mutate the notifications to avoid duplicatation sending
}

Is there a more efficient way to do be doing this?

What is the best way to match on the time, do I have to compare time.Now()’s attributes like year, month, day, hour and minute individually in my if statement? i.e. A notification is triggered if the year,month,day,hour and minute have been reached (seconds and beyond are ignored)

Solution

First things first, to compare time values, use the Time.Equal, Time.Before, and time.After methods. Comparing the individual components is not reliable at all:

newYork, _ := time.LoadLocation("America/New_York")

t1 := time.Date(2018, 11, 8, 4, 0, 0, 0, time.UTC)
t2 := t1.In(newYork)

fmt.Printf("%v == %v?\n", t1, t2) // 2018-11-08 04:00:00 +0000 UTC == 2018-11-07 23:00:00 -0500 EST?

fmt.Println(t1.Day() == t2.Day()) // false
fmt.Println(t2.Equal(t1))         // true

https://play.golang.org/p/06RcvuI_1Ha


For the scheduling problem I would use a time.Timer.

  1. Figure out which notification is up next
  2. Set or reset the timer accordingly
    1. After the timer fires, goto 1
    2. If a notification is added, goto 1
    3. If a notification is deleted, goto 1

Here is a sketch:

package main

import "time"

func main() {
    t := time.NewTimer(0)

    go func() {
        for range t.C {
            nextTwo := db.GetNextNotifications(2)

            // Sanity check
            if time.Until(nextTwo[0].Start) > 1*time.Second {
                // The timer went off early. Perhaps the notification has been
                // deleted?
                t.Reset(time.Until(nextTwo[0].Start))
                continue
            }

            go send(nextTwo[0])
            t.Reset(time.Until(nextTwo[1].Start))
        }
    }()

    resetTimer(t) // call as required whenever a notification is added or removed
}

func resetTimer(t *time.Timer) {
    next := db.GetNextNotification()
    t.Reset(time.Until(next.Start))
}

Answered By – Peter

Answer Checked By – Pedro (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.