Django ejecuta tareas (posiblemente) en el futuro lejano

9

Supongamos que tengo un modelo Event. Quiero enviar una notificación (correo electrónico, push, lo que sea) a todos los usuarios invitados una vez que haya transcurrido el evento. Algo en la línea 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")

Ahora, por supuesto, la parte crucial es invocar onEventElapsedcuando sea timezone.now() >= event.end. Tenga en cuenta que endpodrían estar a meses de la fecha actual.

He pensado en dos formas básicas de hacer esto:

  1. Usar un crontrabajo periódico (digamos, cada cinco minutos más o menos) que verifica si ha transcurrido algún evento en los últimos cinco minutos y ejecuta mi método.

  2. Utilice celeryy programe onEventElapsedutilizando el etaparámetro que se ejecutará en el futuro (dentro del savemétodo de modelos ).

Considerando la opción 1, podría ser una posible solución django-celery-beat. Sin embargo, parece un poco extraño ejecutar una tarea en un intervalo fijo para enviar notificaciones. Además, se me ocurrió un problema (potencial) que (probablemente) resultaría en una solución no tan elegante:

  • ¿Verifica cada cinco minutos los eventos que han transcurrido en los últimos cinco minutos? parece inestable, tal vez algunos eventos se pierdan (¿u otros reciben sus notificaciones enviadas dos veces?) Solución alternativa potencial: agregue un campo booleano al modelo que se establece Trueuna vez que se han enviado las notificaciones.

Por otra parte, la opción 2 también tiene sus problemas:

  • Cuide manualmente la situación cuando se mueve una fecha / hora de inicio / finalización de un evento. Cuando se usa celery, uno tendría que almacenar taskID(easy, ofc) y revocar la tarea una vez que las fechas hayan cambiado y emitir una nueva tarea. Pero he leído que el apio tiene problemas (específicos de diseño) cuando se trata de tareas que se ejecutan en el futuro: Open Issue en github . Me doy cuenta de cómo sucede esto y por qué es todo menos trivial de resolver.

Ahora, he encontrado algunas bibliotecas que podrían resolver mi problema:

  • celery_longterm_scheduler (¿Pero esto significa que no puedo usar el apio como lo hubiera hecho antes, debido a la diferente clase de Scheduler? Esto también se relaciona con el posible uso de django-celery-beat... Usando cualquiera de los dos marcos, ¿aún es posible poner trabajos en cola? ¿son solo un poco más largos pero no faltan meses?)
  • django-apscheduler , usos apscheduler. Sin embargo, no pude encontrar ninguna información sobre cómo manejaría las tareas que se ejecutan en el futuro lejano.

¿Hay un defecto fundemantal con la forma en que me estoy acercando a esto? Estoy contento por cualquier entrada que pueda tener.

Aviso: Sé que es probable que esto se base en alguna opinión, sin embargo, tal vez hay algo muy básico que me he perdido, independientemente de lo que algunos puedan considerar feo o elegante.

Hafnernuss
fuente
1
Diría que su enfoque depende de qué tan pronto después del evento transcurrido el usuario final necesite notificación. Tuve un problema similar en el que el usuario solo necesitaba saber al día siguiente para cualquier cita perdida el día anterior. Entonces, en este caso, ejecuté un trabajo cron a medianoche y, como usted sugirió, tenía un campo booleano para etiquetar si se habían enviado notificaciones. Era una forma muy simple y computacionalmente económica de hacerlo.
Hayden Eastwood
1
En mi opinión, la respuesta es acerca de cuántos eventos necesita enviar. Si tiene que enviar cientos de eventos todos los días, no importa qué tan lejos en el futuro sea un solo evento: utilizando la primera solución (adaptando el tiempo de recurrencia según sus necesidades) puede ejecutar la tarea leyendo datos actualizados.
Dos de
@HaydenEastwood No es crucial que la persona lo reciba de inmediato, pero dentro de 2-5 minutos dentro de la fecha de finalización debería estar bien. ¿Entonces hiciste algo similar a mi opion 1?
Hafnernuss
1
@Hafnernuss Sí, creo que una simple llamada cron con un campo en la base de datos para saber si se envió un mensaje sería una buena opción para su caso.
Hayden Eastwood
1
Dramatiq usa otro enfoque que no sea Apio cuando elimina tareas (no necesita mucha memoria del trabajador) y podría funcionar en su caso, consulte dramatiq.io/guide.html#scheduling-messages . Pero como dicen, el intermediario de mensajes no es DB, cuando necesita planificar un evento a largo plazo, su primera solución es mejor. Por lo tanto, puede combinar ambos: coloque los eventos en MB, digamos por 1 día, y al vencimiento, irán a DB y se enviarán a través de cron.
frost-nzcr4

Respuestas:

2

Estamos haciendo algo así en la empresa para la que trabajo, y la solución es bastante simple.

Tenga un ritmo de cron / apio que se ejecuta cada hora para verificar si es necesario enviar alguna notificación. Luego envíe esas notificaciones y márquelas como hechas. De esta manera, incluso si su tiempo de notificación se adelanta años, aún se enviará. Usar ETA NO es el camino a seguir durante un tiempo de espera muy largo, su caché / amqp podría perder los datos.

Puede reducir su intervalo según sus necesidades, pero asegúrese de que no se superpongan.

Si una hora es una gran diferencia horaria, entonces lo que puede hacer es ejecutar un programador cada hora. La lógica sería algo como

  1. ejecutar una tarea (llamemos a esta tarea del planificador) cada hora que recibe todas las notificaciones que deben enviarse en la próxima hora (a través del apio) -
  2. Programe esas notificaciones a través de apply_async (eta): este será el envío real

Usar esa metodología te llevaría a los dos mejores mundos (eta y beat)

ibaguio
fuente
1
Gracias. ¡Eso es exactamente lo que he hecho!
Hafnernuss