Blogue

Carregar duas vezes em Publicar agora publica apenas uma vez

Três pedidos de publicação para a mesma app chegaram à nossa API com 84 milissegundos de diferença, e cada um iniciou um deploy completo dessa app. Corriam uns contra os outros para o mesmo alojamento, vários dos lançamentos resultantes falharam porque outro tinha chegado primeiro, e o único sinal visível era um email de «lançamento falhou» — a app acabou por ficar bem. Agora, uma publicação que chega enquanto outra já está a correr para a mesma app não inicia um segundo build. A base de dados decide qual pedido ganha, e os outros recebem a verdade: já há uma publicação em curso.

O que aconteceu

Quando publica uma app na Mythex, a API regista um deployment para ela e pede ao nosso orquestrador — o serviço que executa os builds — que a construa e lance. A publicação pode começar a partir do botão Publicar, de outro separador do browser, ou do agente quando lho pede. Um duplo clique, dois separadores, ou um pedido do agente que chega duas vezes podem todos produzir mais do que uma publicação para a mesma app quase ao mesmo tempo.

Neste caso, chegaram três dentro de 84 ms. Cada um correu um deploy completo. Na plataforma de alojamento, os lançamentos da mesma app foram criados no mesmo segundo, e três deles falharam porque os outros já estavam em curso. Um lançamento posterior bloqueou, e a app funcionou depois.

Foi isso que tornou o problema fácil de passar despercebido. O utilizador viu uma app a funcionar. As falhas só apareciam nos emails de falha de lançamento.

Porque nada o impedia

O código de publicação tinha, de facto, um passo que olhava para os deployments em curso do projeto. Mas o que fazia era marcar todos os deployments em curso como falhados antes de começar — «substituído por uma nova publicação». Destinava-se a limpar builds deixados para trás por um processo que tinha crashado.

Na prática, uma segunda publicação que chegasse a meio de um build declarava o primeiro morto e lançava o seu próprio. Os dois corriam até ao fim contra a mesma app. Não havia bloqueio; havia um passo que, ativamente, abria caminho para uma corrida.

Havia também um sintoma mais estranho na primeira publicação de uma app. Sem um registo de deployment para reutilizar, duas primeiras publicações simultâneas tentavam ambas inserir um. A base de dados tem um índice único no nome da ligação da app (o seu slug, como a-sua-app em a-sua-app.mythex.ai), por isso a segunda inserção falhava. A API traduzia isso como «esse nome de ligação já está em uso» — aconselhando o utilizador a mudar o nome de uma app que, naquele momento, estava a ser publicada com sucesso com esse nome.

A correção: a reivindicação é o bloqueio

Reivindicar o registo do deployment é agora uma única atualização condicional na base de dados: marcar esta linha como em build apenas se ninguém estiver a publicá-la. Como é uma única instrução, a base de dados resolve pedidos que chegam no mesmo milissegundo. Exatamente um deles altera a linha. Os outros veem que zero linhas mudaram, param, e nunca chegam ao orquestrador.

Um bloqueio também precisa de uma saída quando o seu detentor morre. Se a API reiniciasse a meio de uma publicação, a linha ficaria marcada como «em build» para sempre e a app ficaria impossível de publicar. Por isso, uma linha cujo dono deixou de enviar heartbeats — a publicação em curso atualiza a linha a cada 30 segundos, e cinco minutos de silêncio contam como morte — pode ser reivindicada de novo. Esse limite vem do heartbeat, e não de um palpite sobre quanto tempo um build demora. Construir um Dockerfile próprio do utilizador pode legitimamente demorar muito, e um build lento que continua a enviar heartbeats mantém a sua reivindicação.

O passo que marcava como falhados todos os deployments em curso já não corre antes de uma publicação. Só são limpas as linhas que um processo morto deixou para trás.

Dizer a verdade ao que perde

O pedido que perde recebe agora uma resposta clara: publish_in_progress, com o ID do deployment que está a correr. Cada sítio que pode iniciar uma publicação trata isso:

  • O espaço de trabalho segue o build que já está em curso, em vez de mostrar um erro por cima dele.
  • O agente é informado de que já há uma publicação desta app em curso e que deve esperar por ela, em vez de ser informado de que algo falhou — o que antes podia levá-lo a «corrigir» um Dockerfile e a tentar de novo.
  • Uma colisão de primeira publicação no nome da ligação é verificada: se a linha que tem o nome for a publicação em curso deste próprio projeto, a resposta é «já está a publicar», e não «esse nome está em uso». Uma colisão real com outro projeto continua a receber a mensagem de mudar de nome.

Como verificámos

Escrevemos um script de reprodução que executa três publicações da mesma app ao mesmo tempo, contra uma base de dados real e um orquestrador simulado, e conta quantos deploys o orquestrador recebe. Cobre uma primeira publicação e uma republicação de uma app ativa. Antes da correção, contava dois deploys. Depois, um.

O script também cobre a saída de emergência: marca a linha da app como em build sem heartbeat durante dez minutos, publica de novo, e verifica que a nova publicação reivindica a linha e corre.

Os testes unitários mantêm o bloqueio às duas coisas que um bloqueio tem de fazer: de três republicações simultâneas, exatamente uma passa e as outras duas são informadas de que uma publicação está em curso; e um build cujo dono morreu pode ser reivindicado. Outro teste verifica que uma nova tentativa não herda a mensagem de erro da tentativa anterior.

O que tirámos disto

  • O código de limpeza pode ser uma corrida disfarçada. «Falhar tudo o que está em curso e depois começar» parece higiene. Com dois chamadores, é cada um a cancelar o outro.
  • Deixe a base de dados arbitrar. Uma leitura seguida de uma escrita deixa uma brecha para outro pedido. Uma única atualização condicional não deixa.
  • Todo o bloqueio precisa de expirar em função da vivacidade. Sem isso, um crash transforma-se numa app bloqueada.
  • As mensagens de erro são instruções. «Esse nome já está em uso» levava as pessoas a fazer a coisa errada. A mensagem certa era mais simples e verdadeira.

Mais sobre publicação na documentação: como publicar. E uma correção relacionada: a sua app publicada continua no ar enquanto a republica.

Mythex · Pricing · Crie qualquer coisa com IA · Modelos de sites e apps · Mythex comparado · Glossário de criação de apps com IA · Ferramentas grátis para criar apps com IA · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)