Django esegue le attività (possibilmente) in un lontano futuro


9

Supponiamo che io abbia un modello Event. Desidero inviare una notifica (e-mail, push, qualunque cosa) a tutti gli utenti invitati una volta trascorso l'evento. Qualcosa sulla falsariga di:

class Event(models.Model):
    start = models.DateTimeField(...)
    end = models.DateTimeField(...)
    invited = models.ManyToManyField(model=User)

    def onEventElapsed(self):
        for user in self.invited:
           my_notification_backend.sendMessage(target=user, message="Event has elapsed")

Ora, ovviamente, la parte cruciale è invocare onEventElapsedogni volta timezone.now() >= event.end. Tieni presente che endpotrebbero mancare mesi alla data corrente.

Ho pensato a due modi base per farlo:

  1. Usa un cronlavoro periodico (diciamo, ogni cinque minuti circa) che controlla se sono trascorsi eventi negli ultimi cinque minuti ed esegue il mio metodo.

  2. Utilizzare celerye pianificare onEventElapsedutilizzando il etaparametro da eseguire in futuro (con il savemetodo dei modelli ).

Considerando l'opzione 1, una potenziale soluzione potrebbe essere django-celery-beat. Tuttavia, sembra un po 'strano eseguire un'attività a intervalli fissi per l'invio di notifiche. Inoltre ho escogitato un (potenziale) problema che avrebbe (probabilmente) comportato una soluzione non così elegante:

  • Controlla ogni cinque minuti gli eventi che sono trascorsi nei cinque minuti precedenti? sembra traballante, forse alcuni eventi sono mancati (o altri ricevono le loro notifiche inviate due volte?). Soluzione alternativa potenziale: aggiungere un campo booleano al modello impostato su Truedopo l'invio delle notifiche.

Quindi, anche l'opzione 2 ha i suoi problemi:

  • Gestire manualmente la situazione quando si sposta un datetime di inizio / fine di un evento. Durante l'utilizzo celery, si dovrebbe archiviare taskID(easy, ofc) e revocare l'attività una volta che le date sono state modificate ed emettere una nuova attività. Ma ho letto che il sedano ha problemi (specifici del design) quando si affrontano attività che verranno eseguite in futuro: Open Issue su github . Mi rendo conto di come ciò accada e perché è tutt'altro che banale da risolvere.

Ora, ho trovato alcune librerie che potrebbero potenzialmente risolvere il mio problema:

  • celery_longterm_scheduler (Ma questo significa che non posso usare il sedano come avrei fatto prima, a causa della diversa classe Scheduler? Ciò si collega anche al possibile utilizzo di django-celery-beat... Utilizzando uno dei due framework, è ancora possibile mettere in coda i lavori (che sono solo un po 'più lunghi ma non a mesi?)
  • django-apscheduler , usa apscheduler. Tuttavia, non sono stato in grado di trovare alcuna informazione su come gestire le attività che verranno eseguite in un futuro lontano.

C'è un difetto fondamentale nel modo in cui mi sto avvicinando a questo? Sono contento per qualsiasi input tu possa avere.

Nota: so che è probabile che questo sia basato sull'opinione di qualcuno, tuttavia, forse c'è qualcosa di molto semplice che mi sono perso, indipendentemente da ciò che alcuni potrebbero considerare brutti o eleganti.


1
Direi che il tuo approccio dipende da quanto tempo dopo l'evento trascorso l'utente finale deve essere avvisato. Ho avuto un problema simile in cui l'utente doveva solo sapere il giorno seguente per qualsiasi appuntamento mancato il giorno precedente. Quindi in questo caso ho eseguito un lavoro cron a mezzanotte e, come hai suggerito, avevo un campo booleano per taggare se le notifiche erano state inviate. Era un modo molto semplice e poco costoso per farlo.
Hayden Eastwood,

1
A mio avviso, la risposta riguarda quanti eventi devi inviare. Se hai centinaia di eventi da inviare ogni giorno, non importa quanto in futuro sia un singolo evento: utilizzando la prima soluzione (adattando il tempo di ricorrenza in base alle tue esigenze) puoi eseguire l'attività leggendo i dati aggiornati.
Dos

@HaydenEastwood Non è fondamentale che la persona lo riceva immediatamente, ma entro 2-5 minuti entro la data di fine dovrebbe andare bene. Quindi hai fatto qualcosa di simile al mio opion 1?
Hafnernuss,

1
@Hafnernuss Sì - Penso che una semplice chiamata cron con un campo nel database per sapere se il messaggio è stato inviato sarebbe adatta al tuo caso.
Hayden Eastwood,

1
Dramatiq usa un altro approccio rispetto al sedano quando evita le attività (non la memoria ha fame del lavoratore) e potrebbe funzionare nel tuo caso, vedi dramatiq.io/guide.html#scheduling-messages . Ma come si suol dire - il broker dei messaggi non è DB - quando è necessario pianificare eventi a lungo termine, la prima soluzione è migliore. Quindi puoi combinare entrambi: inserisci gli eventi in MB, diciamo entro 1 giorno, e alla scadenza andrebbero su DB e saranno inviati via cron.
frost-nzcr4,

Risposte:


2

Stiamo facendo qualcosa del genere nell'azienda per cui lavoro e la soluzione è abbastanza semplice.

Fai battere un cron / sedano ogni ora per verificare se è necessario inviare una notifica. Quindi inviare quelle notifiche e contrassegnarle come completate. In questo modo, anche se il tempo di notifica è avanti di anni, verrà comunque inviato. L'uso di ETA NON è la strada da percorrere per un tempo di attesa molto lungo, la tua cache / amqp potrebbe perdere i dati.

Puoi ridurre l'intervallo in base alle tue esigenze, ma assicurati che non si sovrappongano.

Se un'ora è una differenza oraria troppo grande, allora ciò che puoi fare è eseguire un programmatore ogni ora. La logica sarebbe qualcosa di simile

  1. eseguire un'attività (consente di chiamare questa attività di pianificazione) ogni ora che riceve tutte le notifiche che devono essere inviate nell'ora successiva (tramite battuta di sedano) -
  2. Pianifica tali notifiche tramite apply_async (eta) - questo sarà l'invio effettivo

L'uso di quella metodologia ti farebbe ottenere entrambi i migliori mondi (eta e beat)


1
Grazie. Questo è esattamente quello che ho fatto!
Hafnernuss,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.