Aprenda / Ruby on Rails

IntermediárioAula 17 de 2014 min0% da trilha

Background jobs com Solid Queue

Se a resposta HTTP só pode voltar depois que um e-mail terminar de ser enviado, o usuário está esperando por algo que não devia bloquear a requisição.

Enviar e-mail, chamar uma API externa lenta, gerar um relatório pesado — nada disso precisa (nem deveria) rodar dentro do ciclo de um request. Se acontece ali, o usuário fica esperando a resposta até esse trabalho terminar, e se o serviço externo estiver lento ou fora do ar, o request inteiro trava ou estoura timeout por causa de algo que não tinha relação direta com o que ele pediu.

deliver_now vs. deliver_later — a diferença que a lição de service objects não explicou

O service FinalizarPedido de lições anteriores já usava PedidoMailer.confirmacao(@pedido).deliver_later sem detalhar o porquê. Compare:

bloqueia a resposta
PedidoMailer.confirmacao(@pedido).deliver_now   # espera o servidor de e-mail responder antes de continuar
não bloqueia — enfileira e segue
PedidoMailer.confirmacao(@pedido).deliver_later   # devolve na hora; o envio roda depois, fora do request

deliver_later não envia nada na hora — ele enfileira um job via Active Job. Quem realmente processa essa fila depois é o adapter configurado no projeto; em apps Rails mais novos, o padrão passou a ser o Solid Queue.

O que o Solid Queue resolve

Solid Queue grava os jobs pendentes numa tabela do próprio banco de dados relacional da aplicação, em vez de depender de um serviço externo à parte (como Redis, que ferramentas mais antigas como Sidekiq exigem) só pra manter a fila. Um processo separado (o worker) lê essa tabela e executa os jobs pendentes, na ordem e com as tentativas configuradas.

Um job próprio, além de mailer

app/jobs/notificar_estoque_baixo_job.rb
class NotificarEstoqueBaixoJob < ApplicationJob
  queue_as :default
  retry_on StandardError, wait: :polynomially_longer, attempts: 5

  def perform(produto)
    return unless produto.estoque < 10
    EstoqueMailer.alerta_baixo(produto).deliver_now
  end
end
enfileirando o job
NotificarEstoqueBaixoJob.perform_later(produto)

perform_later enfileira o job e retorna imediatamente — quem chamou não espera perform rodar. retry_on StandardError, wait: :polynomially_longer, attempts: 5 diz ao Active Job: se perform levantar essa exceção, tente de novo automaticamente, até 5 vezes, esperando mais tempo a cada nova tentativa. Sem isso, uma falha temporária (rede instável, serviço externo fora do ar por alguns segundos) perderia o job silenciosamente, sem nenhuma nova tentativa.

Exercício

Uma action hoje chama RelatorioService.new(pedido).gerar_e_enviar de forma síncrona, travando a resposta até o relatório terminar. Transforme isso num job assíncrono chamado por essa action.

Quiz rápido

Por que enviar e-mail dentro do ciclo do request, com deliver_now, é um problema em produção?
O que retry_on StandardError, wait: :polynomially_longer, attempts: 5 configura num job?