O Django executa tarefas (possivelmente) em um futuro distante

9

Suponha que eu tenho um modelo Event. Quero enviar uma notificação (email, push, qualquer que seja) a todos os usuários convidados depois que o evento terminar. Algo ao longo das linhas 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")

Agora, é claro, a parte crucial é invocar onEventElapsedsempre timezone.now() >= event.end. Lembre-se de que endpode demorar meses para a data atual.

Eu pensei em duas maneiras básicas de fazer isso:

  1. Use um crontrabalho periódico (digamos, a cada cinco minutos mais ou menos) que verifique se algum evento ocorreu nos últimos cinco minutos e execute meu método.

  2. Use celerye programe onEventElapsedusando o etaparâmetro a ser executado no futuro (dentro do savemétodo de modelos ).

Considerando a opção 1, uma solução potencial poderia ser django-celery-beat. No entanto, parece um pouco estranho executar uma tarefa em um intervalo fixo para enviar notificações. Além disso, eu vim com um problema (potencial) que provavelmente resultaria em uma solução não tão elegante:

  • Verifique a cada cinco minutos se há eventos decorridos nos cinco minutos anteriores? parece instável, talvez alguns eventos sejam perdidos (ou outros recebam as notificações duas vezes?). Área de trabalho potencial: adicione um campo booleano ao modelo definido como Trueuma vez que as notificações forem enviadas.

Então, novamente, a opção 2 também tem seus problemas:

  • Cuide manualmente da situação quando um horário de início / término do evento for movido. Ao usar celery, seria necessário armazenar o taskID(easy, ofc) e revogar a tarefa assim que as datas mudarem e emitir uma nova tarefa. Mas eu li que o aipo tem problemas (específicos do projeto) ao lidar com tarefas que são executadas no futuro: Open Issue no github . Percebo como isso acontece e por que tudo é trivial de resolver.

Agora, encontrei algumas bibliotecas que poderiam solucionar meu problema:

  • celery_longterm_scheduler (Mas isso significa que não posso usar o aipo como antes, devido à classe diferente do Scheduler? Isso também está relacionado ao possível uso de django-celery-beat... Usando qualquer uma das duas estruturas, ainda é possível enfileirar trabalhos (que demoram um pouco mais, mas não demoram meses?)
  • django-apscheduler , usa apscheduler. No entanto, não consegui encontrar nenhuma informação sobre como ele lidaria com tarefas executadas no futuro distante.

Existe uma falha fundemantal na maneira como estou abordando isso? Fico feliz por quaisquer entradas que você possa ter.

Aviso: Eu sei que é provável que isso seja baseado em opiniões, no entanto, talvez haja uma coisa muito básica que eu tenha perdido, independentemente do que possa ser considerado por alguns como feio ou elegante.

Hafnernuss
fonte
11
Eu diria que sua abordagem depende de quanto tempo após o evento decorrido o usuário final precisa ser notificado. Eu tive um problema semelhante no qual o usuário só precisava saber no dia seguinte para qualquer consulta perdida no dia anterior. Portanto, nesse caso, executei um trabalho cron à meia-noite e tinha, como você sugeriu, um campo booleano para marcar se as notificações foram enviadas. Era uma maneira muito simples e computacionalmente barata de fazer isso.
Hayden Eastwood
11
Na minha opinião, a resposta é sobre quantos eventos você precisa enviar. Se você tiver centenas de eventos a serem enviados todos os dias, não importa o quão longe no futuro seja um único evento: usando a primeira solução (adaptando o tempo de recorrência com base em suas necessidades), você poderá executar a tarefa lendo os dados atualizados.
Dos
@HaydenEastwood Não é crucial que a pessoa o receba imediatamente, mas dentro de 2 a 5 minutos dentro da data de término deve ficar bem. Então você fez algo semelhante à minha opinião 1?
Hafnernuss 28/02
11
@Hafnernuss Sim - acho que uma simples chamada cron com um campo no banco de dados para saber se a mensagem foi enviada seria uma boa opção para o seu caso.
Hayden Eastwood
11
O Dramatiq usa outra abordagem além do Aipo ao descartar tarefas (que não têm muita memória do trabalhador) e pode funcionar no seu caso, consulte dramatiq.io/guide.html#scheduling-messages . Mas, como se costuma dizer - o intermediário de mensagens não é o DB - quando você precisa planejar eventos de longo prazo, sua primeira solução é melhor. Assim, você pode combinar os dois: colocar eventos em MB, digamos, por 1 dia e por vencimento, eles irão para o DB e serão enviados via cron.
frost-nzcr4 28/02

Respostas:

2

Estamos fazendo algo assim na empresa em que trabalho e a solução é bastante simples.

Tenha uma batida cron / aipo que é executada a cada hora para verificar se alguma notificação precisa ser enviada. Em seguida, envie essas notificações e marque-as como concluídas. Dessa forma, mesmo que seu tempo de notificação esteja nos próximos anos, ele ainda será enviado. Usar o ETA NÃO é o caminho a percorrer por um tempo de espera muito longo, seu cache / amqp pode perder os dados.

Você pode reduzir seu intervalo, dependendo de suas necessidades, mas verifique se eles não se sobrepõem.

Se uma hora é muito grande, o que você pode fazer é executar um agendador a cada hora. Lógica seria algo como

  1. executar uma tarefa (vamos chamar essa tarefa do agendador) a cada hora que recebe todas as notificações que precisam ser enviadas na próxima hora (via aipo) -
  2. Programe essas notificações via apply_async (eta) - este será o envio real

Usando essa metodologia, você obteria os dois melhores mundos (eta e beat)

ibaguio
fonte
11
Obrigado. Foi exatamente isso que fiz!
Hafnernuss