Adota Solid Queue como fila persistente em produção - #87
Merged
Merged
Conversation
Closes #77. Nao havia queue_adapter configurado em lugar nenhum, entao valia o default do Rails: :async, que guarda a fila na memoria do processo web. Todo restart, deploy ou OOM descartava em silencio o que ainda nao tinha rodado — inclusive as retentativas de webhook agendadas com backoff pelo PR anterior, que por definicao ficam pendentes por algum tempo. Producao passa a usar :solid_queue, gravando nas tabelas solid_queue_* do proprio PostgreSQL. Desenvolvimento segue com :async, pra que `bin/rails server` sozinho continue funcionando sem exigir um segundo processo; teste segue com :test. Mesmo banco da aplicacao, e nao o banco separado que o instalador da gem assume. Multiplos bancos obrigariam a reescrever config/database.yml inteiro, incluindo o caminho de DATABASE_URL que o Railway injeta, pra resolver um problema de escala que este projeto nao tem. Por isso o db/queue_schema.rb da gem virou uma migration comum. O worker e um servico separado no docker-compose.yml, rodando bin/jobs. Sobe so depois de o web ficar saudavel: e o entrypoint do web que aplica as migrations, e o worker precisa das tabelas existindo. Ele nao roda migration nenhuma de proposito — dois processos preparando o mesmo banco ao mesmo tempo e corrida, nao redundancia. Build e ambiente saem de ancoras YAML compartilhadas, pra que web e worker nao divirjam na primeira variavel nova que alguem esquecer de copiar. Detalhes que ficaram registrados no codigo: * o HEALTHCHECK da imagem faz curl na porta 3000, o que nao faz sentido pro worker — desligado no servico, senao ele ficaria eternamente "unhealthy" por nao responder algo que nem deveria responder; * config/recurring.yml existe vazio so pra o worker parar de avisar que nao encontrou o arquivo, o que viraria ruido permanente no log; * as duas migrations do Solid Queue sao schema da gem copiado, nao codigo nosso: entram na exclusao do RuboCop pelo mesmo criterio ja aplicado ao db/schema.rb. Reformata-las faria o proximo `solid_queue:update` virar conflito. O Gemfile.lock ganhou a plataforma x86_64-linux, efeito de resolver as gems dentro do container Linux. E uma melhoria: o lockfile passa a descrever a plataforma onde a aplicacao de fato roda, e o `bundle lock --add-platform` do Dockerfile continua ali como rede de seguranca. Verificado com o compose de verdade, e nao so com specs: job enfileirado pelo web com o worker PARADO sobreviveu a um restart do web e foi executado quando o worker voltou — exatamente o que o :async nao fazia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hirley
added a commit
that referenced
this pull request
Sep 1, 2026
O smoke test do commit anterior falhou na primeira execução com "container task_keeper_api-worker-1 exited (126)" — 126 é "comando encontrado, mas não executável". bin/jobs entrou no repositório como 100644, enquanto todos os outros scripts de bin/ são 100755. O arquivo foi criado no Windows na #77, e o sistema de arquivos de lá não tem bit de execução, então o Git registrou o modo sem ele. Um `docker build` nesta máquina não percebe: o contexto vem do disco, onde tudo parece executável — foi por isso que a verificação da #77 passou aqui. Um checkout num runner Linux respeita o modo do índice, e aí o worker não sobe. Ou seja: a imagem publicada em main a partir da #87 builda perfeitamente e não tem worker. Como o web responde normalmente e a falha é do outro processo, o sintoma seria "os jobs não saem da fila" — exatamente o modo de falha silencioso que a #77 veio eliminar. Duas camadas, mesmo critério já usado para o CRLF: * `git update-index --chmod=+x bin/jobs` corrige a causa, no índice; * `chmod +x bin/*` no Dockerfile, ao lado do `sed` que normaliza LF, protege a imagem do próximo script criado no Windows. A armadilha foi para o CLAUDE.md, junto com outra que apareceu na verificação: exportar MSYS_NO_PATHCONV=1 no shell inteiro faz o curl do Git Bash falhar com "(23)" em `-o /dev/null`, o que parece erro da aplicação e não é. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #77. Fecha o achado de alta prioridade da avaliação técnica: a fila vivia na memória do processo web.
Não havia
queue_adapterconfigurado em lugar nenhum, então valia o default do Rails —:async. Todo restart, deploy ou OOM descartava em silêncio o que ainda não tinha rodado, incluindo as retentativas de webhook que o #86 acabou de introduzir e que, por definição, ficam pendentes por algum tempo.A prova, com o compose de verdade
Specs não cobrem isto — o adapter em teste é o
:test. Então verifiquei subindo a stack:O passo 2 é o ponto: com
:async, aquele job teria desaparecido.Decisões
Mesmo banco da aplicação, e não o banco separado que o instalador do Solid Queue assume. Multi-banco obrigaria a reescrever o
config/database.ymlinteiro — incluindo o caminho deDATABASE_URL, que é o que o Railway injeta — para resolver um problema de escala que este projeto não tem. Odb/queue_schema.rbda gem virou uma migration comum, com o motivo registrado nela.Produção usa
:solid_queue; desenvolvimento continua no:async. Exigir um segundo processo parabin/rails serverquebraria o fluxo de setup que o README documenta. A durabilidade importa onde há deploy.O worker espera o
webficar saudável, não só o banco. É o entrypoint dowebque aplica as migrations; subir antes faria o worker procurar tabelas que ainda não existem. E ele não roda migration nenhuma de propósito — dois processos preparando o mesmo banco ao mesmo tempo é corrida, não redundância. (Isso encosta na #82, que trata de tirar as migrations do boot do web; aqui só garanti que o worker não piora o quadro.)Build e ambiente saem de âncoras YAML compartilhadas. Duas listas separadas divergiriam na primeira variável nova que alguém esquecesse de copiar, e o sintoma seria um job se comportando diferente da tela — caro de diagnosticar.
Três detalhes que valem o comentário que receberam
HEALTHCHECKda imagem fazcurlna porta 3000. Faz sentido para oweb, não para oworker, que não serve HTTP — desligado no serviço, senão ficaria eternamenteunhealthypor não responder algo que nem deveria responder.config/recurring.ymlexiste vazio só para o worker parar de avisar que não encontrou o arquivo. Ruído permanente num log é ruído que se aprende a ignorar.db/schema.rb. Sem isso são 287 ofensas, e reformatá-las faria o próximosolid_queue:updatevirar conflito.Efeito colateral que vale declarar
O
Gemfile.lockganhou a plataformax86_64-linux, resultado de resolver as gems dentro do container. É uma melhoria — o lockfile passa a descrever a plataforma onde a aplicação de fato roda, e era inclusive uma das recomendações da avaliação. Obundle lock --add-platformdoDockerfilecontinua ali como rede de segurança, agora como no-op.Verificação
All is good!docker compose up --buildO que continua em aberto
O adapter de desenvolvimento segue
:async, então lá os jobs ainda se perdem — documentado no README, e é o trade-off deliberado acima. E a #82 (migrations acopladas ao boot do web) fica mais visível agora que há dois processos: hoje o worker depende do web ter subido para que o banco esteja pronto.🤖 Generated with Claude Code