Pular para o conteúdo
Development

Tornando os scans do blog assíncronos com Solid Queue (e uma pegadinha do Rails)

Por Victor Da Luz
railssolid-queueactivejobdev-logblog-manager

Adicionei um botão de Scan ao blog-manager algumas semanas atrás. Você clica, ele chama a API do GitHub para cada post do repositório, atualiza o banco de dados e mostra um resumo. Simples o bastante. E funcionou bem enquanto o scan era pequeno.

Aí o scan passou de 300 chamadas de API.

O scan começou a estourar o timeout. Não porque o Rails estava travando, o job terminava normalmente. Mas o kamal-proxy tem um timeout de ~30s, e 300 chamadas de API do GitHub levam cerca de 55 segundos. O navegador recebia um 504. O Rails terminava mesmo assim e atualizava o banco de dados, completamente em silêncio. Nada bom.

A solução era óbvia: mover para um job em segundo plano. O Solid Queue já estava no Gemfile por padrão do Rails 8.1. Eu só ainda não tinha escrito meu primeiro job.

Montando a estrutura

O primeiro job de qualquer app Rails define o padrão para tudo que vem depois, então passei um tempo pensando no formato antes de escrever uma linha sequer.

O job precisava fazer três coisas:

  1. Rastrear o estado no registro Blog (idle, running, failed) para que a interface pudesse refletir o que estava acontecendo sem fazer polling num endpoint
  2. Guardar o resumo do último resultado (last_scan_summary) para que os usuários pudessem ver o que aconteceu depois que o job terminasse
  3. Tratar os erros corretamente - falhas de autenticação e 404 deveriam falhar rápido, erros transitórios do GitHub deveriam tentar de novo

Adicionei um enum scan_state e uma coluna de texto last_scan_summary em blogs. O job define running no início, e depois idle (com o resumo do resultado) ou failed (com o erro) no final.

A pegadinha

O ActiveJob do Rails oferece duas ferramentas declarativas: retry_on para falhas transitórias e discard_on para falhas permanentes. Eu queria que erros de autenticação e 404 fossem descartados imediatamente, e que erros genéricos do GitHub tentassem de novo 3 vezes.

Escrevi o job com discard_on primeiro, depois retry_on. Meus testes para o caminho de descarte falharam, o blog ficava travado no estado :running.

A causa: o Rails usa rescue_from por baixo dos panos para os dois, e rescue_from usa uma pilha LIFO. Último registrado, primeiro verificado. Eu tinha registrado discard_on primeiro e retry_on depois, então retry_on ficava no topo da pilha. Como AuthError herda de Error, retry_on Github::ContentClient::Error já dava match antes de discard_on sequer rodar.

A solução: inverter a ordem. Definir retry_on primeiro, depois discard_on. Agora discard_on fica no topo da pilha e captura os erros de autenticação antes de retry_on chegar a vê-los.

# CORRECT
retry_on Github::ContentClient::Error, wait: :polynomially_longer, attempts: 3

discard_on Github::ContentClient::AuthError, Github::ContentClient::NotFoundError do |job, error|
  job.arguments.first.update!(scan_state: :failed, last_scan_summary: "...")
end

Eu não tinha visto isso documentado com clareza em lugar nenhum. Faz sentido assim que você sabe que rescue_from é LIFO, mas é fácil passar batido.

Mission Control

Com os jobs rodando em segundo plano, eu queria visibilidade. A gem companheira do Solid Queue, mission_control-jobs, adiciona um dashboard estilo Sidekiq em qualquer caminho onde você a montar. Montei em /jobs em uns cinco minutos.

Uma nota de configuração: definir base_controller_class para usar a autenticação própria do app não basta sozinho. Você também precisa de http_basic_auth_enabled = false, senão os dois mecanismos de autenticação rodam e o HTTP Basic vence.

O que vem a seguir

A interface ainda exige um refresh manual para ver os resultados do job. Isso é a montagem do Turbo Streams.

Leitura relacionada

Você também pode achar útil

NordPass

NordPass

Gerenciador de senhas da equipe por trás da NordVPN, com um plano gratuito.

Como afiliado da NordPass, ganho com compras qualificadas.

Saiba mais
Airalo

eSIM Airalo

eSIM de dados local para viagens - sem necessidade de trocar um SIM físico.

Este é meu link de indicação da Airalo. Você recebe um desconto no seu primeiro eSIM e eu ganho crédito da Airalo para o meu.

Saiba mais
Proton

Proton Pass

Gerenciador de senhas focado em privacidade, da equipe por trás do Proton Mail.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais