Django exécuter des tâches (éventuellement) dans un avenir lointain

9

Supposons que j'ai un modèle Event. Je souhaite envoyer une notification (e-mail, push, etc.) à tous les utilisateurs invités une fois l'événement terminé. Quelque chose dans le sens de:

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")

Maintenant, bien sûr, la partie cruciale est d'invoquer onEventElapsedchaque fois timezone.now() >= event.end. Gardez à l'esprit que cela endpourrait être à des mois de la date actuelle.

J'ai réfléchi à deux façons de procéder:

  1. Utilisez un crontravail périodique (disons toutes les cinq minutes environ) qui vérifie si des événements se sont écoulés au cours des cinq dernières minutes et exécute ma méthode.

  2. Utiliser celeryet planifier en onEventElapsedutilisant le etaparamètre à exécuter dans le futur (dans la saveméthode des modèles ).

En considérant l'option 1, une solution potentielle pourrait être django-celery-beat. Cependant, il semble un peu étrange d'exécuter une tâche à un intervalle fixe pour l'envoi de notifications. De plus, j'ai trouvé un problème (potentiel) qui résulterait (probablement) en une solution pas si élégante:

  • Vérifiez toutes les cinq minutes les événements qui se sont écoulés au cours des cinq minutes précédentes? semble fragile, peut-être que certains événements sont manqués (ou que d'autres reçoivent leurs notifications deux fois?). Workaroung potentiel: ajoutez un champ booléen au modèle qui est défini sur Trueune fois les notifications envoyées.

Là encore, l'option 2 a également ses problèmes:

  • Prenez soin manuellement de la situation lors du déplacement d'un datetime de début / fin d'événement. Lors de l'utilisation celery, il faudrait stocker le taskID(facile, ofc) et révoquer la tâche une fois que les dates ont changé et émettre une nouvelle tâche. Mais j'ai lu que le céleri a des problèmes (spécifiques à la conception) lorsqu'il s'agit de tâches qui seront exécutées à l'avenir: Problème ouvert sur github . Je me rends compte comment cela se produit et pourquoi c'est tout sauf trivial à résoudre.

Maintenant, j'ai rencontré des bibliothèques qui pourraient potentiellement résoudre mon problème:

  • celery_longterm_scheduler (Mais cela signifie que je ne peux pas utiliser le céleri comme je l' aurais avant, à cause de la classe Différend Scheduler? Cela a également des liens dans l'utilisation possible django-celery-beat... En utilisant l' un des deux cadres, est - il encore possible de faire la queue des emplois (qui sont juste un peu plus longs mais pas dans des mois?)
  • django-apscheduler , utilise apscheduler. Cependant, je n'ai pas pu trouver d'informations sur la façon dont il gérerait les tâches exécutées dans un avenir lointain.

Y a-t-il une faille fondamentale dans la façon dont j'aborde cette question? Je suis content pour toutes les entrées que vous pourriez avoir.

Remarque: je sais que cela est probablement basé sur l'opinion de quelque chose, cependant, il y a peut-être une chose très basique que j'ai manquée, indépendamment de ce qui pourrait être considéré par certains comme laid ou élégant.

Hafnernuss
la source
1
Je dirais que votre approche dépend de combien de temps après l'événement écoulé l'utilisateur final a besoin d'une notification. J'ai eu un problème similaire dans lequel l'utilisateur n'avait besoin de connaître le lendemain que pour tout rendez-vous manqué la veille. Dans ce cas, j'ai exécuté un travail cron à minuit et j'avais, comme vous l'avez suggéré, un champ booléen pour marquer si des notifications avaient été envoyées. C'était une façon très simple et peu coûteuse de le faire.
Hayden Eastwood
1
À mon avis, la réponse concerne le nombre d'événements que vous devez envoyer. Si vous avez des centaines d'événements à envoyer chaque jour, peu importe dans quelle mesure à l'avenir est un seul événement: en utilisant la première solution (en adaptant le temps de récurrence en fonction de vos besoins), vous pouvez exécuter la tâche en lisant les données mises à jour.
Dos
@ HaydenEastwood Ce n'est pas crucial que la personne le reçoive immédiatement, mais dans les 2 à 5 minutes avant la date de fin, ça devrait aller. Vous avez donc fait quelque chose de similaire à mon opion 1?
Hafnernuss
1
@Hafnernuss Oui - Je pense qu'un simple appel cron avec un champ dans la base de données pour savoir si le message a été envoyé serait un bon choix pour votre cas.
Hayden Eastwood
1
Dramatiq utilise une approche différente de celle du céleri lors de la suppression des tâches (pas de mémoire pour le travailleur) et pourrait fonctionner dans votre cas, voir dramatiq.io/guide.html#scheduling-messages . Mais comme on dit - le courtier de messages n'est pas une base de données - lorsque vous avez besoin de planifier un événement à long terme, votre première solution est meilleure. Vous pouvez donc combiner les deux: mettez les événements en Mo, disons d'ici un jour, et à l'expiration, ils iront à la base de données et seront envoyés via cron.
frost-nzcr4

Réponses:

2

Nous faisons quelque chose comme ça dans l'entreprise pour laquelle je travaille, et la solution est assez simple.

Avoir un rythme cron / céleri qui s'exécute toutes les heures pour vérifier si une notification doit être envoyée. Envoyez ensuite ces notifications et marquez-les comme terminées. De cette façon, même si votre délai de notification est en avance de plusieurs années, il sera toujours envoyé. L'utilisation d'ETA n'est PAS la voie à suivre pendant un temps d'attente très long, votre cache / amqp peut perdre les données.

Vous pouvez réduire votre intervalle en fonction de vos besoins, mais assurez-vous qu'ils ne se chevauchent pas.

Si une heure représente un décalage horaire trop important, vous pouvez exécuter un planificateur toutes les heures. La logique serait quelque chose comme

  1. exécuter une tâche (permet d'appeler cette tâche du planificateur) toutes les heures qui reçoit toutes les notifications qui doivent être envoyées dans l'heure suivante (via céleri beat) -
  2. Planifiez ces notifications via apply_async (eta) - ce sera l'envoi réel

L'utilisation de cette méthodologie vous procurerait les deux meilleurs mondes (eta et beat)

ibaguio
la source
1
Je vous remercie. C'est exactement ce que j'ai fait!
Hafnernuss