Por que o deploy zero-downtime não é um luxo, mas uma necessidade para o desenvolvedor Swift
\nImagine: seu serviço Swift no Vapor processa milhares de requisições por segundo. Você lança uma nova funcionalidade — e os usuários recebem 502 Bad Gateway, e nos logs — SIGTERM e conexões interrompidas. Familiar? Se você programa em Swift para backend, mais cedo ou mais tarde enfrentará o problema: como atualizar o serviço sem perder nenhuma requisição.
Deploy zero-downtime não é mágica, mas um conjunto de técnicas comprovadas que permitem substituir um binário Swift em execução por um novo, sem interromper conexões ativas. Neste artigo, mostrarei como configurar o graceful shutdown, organizar a troca a quente de processos e integrar o serviço Swift com o Nginx. Tudo — com código real em Swift e sem enrolação.
\n\nO que é zero-downtime e por que é importante para projetos Swift
\nZero-downtime deployment (deploy sem interrupções) é quando você atualiza a aplicação e os usuários não percebem nem um milissegundo de parada. Para serviços Swift, isso é especialmente importante porque:
\n- \n
- Clientes não toleram erros — uma requisição interrompida = dados ou dinheiro perdidos. \n
- Servidores Swift frequentemente operam sob alta carga (fintech, chats, API em tempo real). \n
- A reinicialização do binário pode levar segundos — e isso é uma eternidade para uma conexão ativa. \n
A abordagem clássica — matar o processo antigo e iniciar um novo — não funciona. A aplicação Swift no Vapor ou Hummingbird deve finalizar suavemente o processamento das requisições atuais e então ceder lugar à nova instância. É exatamente sobre isso — todo o código a seguir.
\n\nPasso 1. Graceful shutdown em Swift: escrevendo um encerramento correto
\nO primeiro tijolo do zero-downtime é o graceful shutdown. Seu serviço Swift deve ser capaz de capturar o sinal SIGTERM e não interromper conexões pela metade. O Vapor 4+ já faz isso nativamente, mas vamos verificar e, se necessário, adicionar lógica personalizada.
Configuração básica do Vapor para graceful shutdown
\nimport Vapor\n\nlet app = Application()\n\n// Registramos o manipulador de sinal\napp.lifecycle.use(ShutdownSignalHandler())\n\n// Código padrão do Vapor\napp.get("health") { req in\n return "OK"\n}\n\ntry app.run()\n\nMas e se você precisar aguardar a conclusão de tarefas longas? Por exemplo, processamento de arquivos ou envio de e-mails. Vamos adicionar um ShutdownSignalHandler personalizado:
import Vapor\nimport NIOCore\n\nfinal class ShutdownSignalHandler: LifecycleHandler {\n private var runningTasks: [EventLoopFuture<Void>] = []\n \n func willBoot(_ app: Application) throws {\n // Assinamos o SIGTERM\n app.running?.onStop = {\n print("⏳ Sinal de parada recebido. Finalizando tarefas ativas...")\n return self.runningTasks.flatten(on: app.eventLoopGroup.next())\n }\n }\n \n func registerTask(_ task: EventLoopFuture<Void>) {\n runningTasks.append(task)\n }\n}\n\nAgora, quando chegar o SIGTERM, o servidor aguardará a conclusão de todas as tarefas registradas. Isso é crítico para o deploy zero-downtime de serviços Swift: o processo antigo não matará as requisições que já começou a processar.
Verificando no console
\nInicie o servidor e envie um sinal para ele:
\nkill -SIGTERM <PID>\n\nNos logs, você verá uma mensagem sobre o encerramento suave. Se tudo funcionar — passamos para o próximo passo.
\n\nPasso 2. Deploy blue-green em Swift: dois binários — nenhuma interrupção
\nO graceful shutdown resolve o problema de interrupção de conexões, mas não resolve o problema do tempo de atualização. Enquanto o processo antigo está finalizando, o novo já deve estar aceitando requisições. O esquema ideal é o deploy blue-green.
\nA ideia é simples: você tem duas instâncias do serviço Swift (blue e green). Uma está ativa, a outra — em modo de espera. Quando você atualiza o código, você:
\n- \n