Skip to content

Separa migrations do boot do web, com uma etapa de release - #93

Merged
Hirley merged 1 commit into
mainfrom
claude-hirley/migrations-release
Sep 1, 2026
Merged

Hirley merged 1 commit into
mainfrom
claude-hirley/migrations-release

Conversation

@Hirley

@Hirley Hirley commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Fecha a #82.

bin/docker-entrypoint roda db:prepare antes do Puma toda vez que o container sobe. É o que faz docker compose up --build funcionar de primeira, e continua sendo o default — mas passou a ser desligável com DB_PREPARE_ON_BOOT=false, que é o que um deploy de verdade deve fazer, rodando as migrations numa etapa de release antes de subir as réplicas.

Quando Como
No boot (default) docker compose up, docker run, demo de uma réplica nada a fazer
Etapa de release qualquer deploy real, obrigatório com mais de uma réplica DB_PREPARE_ON_BOOT=false + migrations antes das réplicas

A premissa da issue precisava de correção

Ela falava em "todas as réplicas tentam preparar o banco ao mesmo tempo", sugerindo risco de corrupção. O schema nunca esteve em risco: as migrations do Rails pegam um advisory lock no Postgres — verifiquei em activerecord-8.1.3.1, Migrator#with_advisory_lock.

O que acontece de verdade é pior de um jeito diferente. O lock é obtido com pg_try_advisory_lock, que não bloqueia: quem perde recebe false e o Rails levanta ConcurrentMigrationError. Como o entrypoint roda com bash -e, o db:prepare que falha derruba o container — a réplica morre no boot em vez de subir depois da que migrou.

Somado ao acoplamento que a issue já apontava (boot preso no tempo e no sucesso da migration), esse é o argumento inteiro. Está escrito assim no entrypoint e no README, no lugar da versão imprecisa.

Duas decisões

O default continua ligado. Inverter deixaria o docker run documentado no README e o compose local subindo contra um banco vazio — um jeito pior de falhar do que o problema evitado. Quem faz deploy de verdade liga o modo release explicitamente, e a documentação diz isso.

A etapa de release não precisou de comando novo. O entrypoint só prepara o banco quando o comando é ./bin/rails server, então docker compose run --rm web ./bin/rails db:prepare já passa direto. Isso virou um passo do smoke test: sem ele, seria um caminho documentado que nada exercita — a mesma lacuna que o smoke test da #81 veio fechar para a imagem inteira.

Verificação

  • a condição do entrypoint nas cinco combinações que importam (server com a variável vazia / true / false, db:prepare, bin/jobs) — só a primeira e a terceira preparam;
  • docker compose config com e sem a variável, resolvendo para "" e "false";
  • gates de sempre no container: RuboCop 110 arquivos / 0 ofensas, zeitwerk:check All is good!, RSpec 394 exemplos / 0 falhas.

O que não verifiquei localmente: o comportamento dentro do container. Exigiria rebuildar a imagem (~10 min nesta máquina, com o Docker Desktop já tendo caído duas vezes hoje), e o passo novo do smoke test roda exatamente isso no CI — que, aliás, é onde as duas últimas diferenças Windows/Linux apareceram.

bin/docker-entrypoint roda db:prepare antes do Puma toda vez que o
container sobe. É o que faz `docker compose up --build` funcionar de
primeira, e continua sendo o default — mas passou a ser desligável com
DB_PREPARE_ON_BOOT=false, que é o que um deploy de verdade deve fazer,
rodando as migrations numa etapa de release antes de subir as réplicas.

Sobre a premissa da issue, que falava em "todas as réplicas tentam
preparar o banco ao mesmo tempo": o schema nunca esteve em risco. As
migrations do Rails pegam um advisory lock no Postgres (verificado em
activerecord-8.1.3.1: Migrator#with_advisory_lock).

O que acontece de verdade é pior de um jeito diferente, e vale a
correção: o lock é obtido com pg_try_advisory_lock, que NÃO bloqueia.
Quem perde recebe false e o Rails levanta ConcurrentMigrationError; como
o entrypoint roda com `bash -e`, o db:prepare que falha derruba o
container. A réplica morre no boot em vez de subir depois da que migrou.
Somado ao acoplamento que a issue já apontava — boot preso no tempo e no
sucesso da migration —, é o argumento inteiro.

O default continua ligado de propósito. Inverter deixaria o `docker run`
documentado no README e o compose local subindo contra um banco vazio,
que é um jeito pior de falhar do que o problema evitado.

A etapa de release não precisou de comando novo: o entrypoint só prepara
o banco quando o comando é `./bin/rails server`, então
`docker compose run --rm web ./bin/rails db:prepare` já passa direto. Isso
virou um passo do smoke test — sem ele seria um caminho documentado que
nada exercita, a mesma lacuna que o smoke test veio fechar na #81.

Verificado: a condição do entrypoint nas cinco combinações que importam
(server com a variável vazia/true/false, db:prepare, bin/jobs), o
docker compose config com e sem a variável, e os gates de sempre no
container — RuboCop 110 arquivos sem ofensas, zeitwerk:check limpo,
RSpec 394 exemplos e 0 falhas. O comportamento dentro do container quem
verifica é o smoke test do CI, que é onde ele de fato roda.

Closes #82

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Hirley
Hirley merged commit ce472ba into main Sep 1, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in @Hirley's task_keeper_api Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant