Deploys Canário São Só Esconder Seus Bugs Atrás de Usuários Inocentes
Nos velhos tempos, fazíamos deploy de software da maneira honesta: enviávamos o binário para produção, segurámos o fôlego e torcíamos para o melhor. Às vezes funcionava. Às vezes não. A empresa inteira ficava sabendo ao mesmo tempo. Era comunidade.
Agora temos “entrega progressiva”. Temos “deploys canário”. Temos “deploys blue-green”. E é pra eu fingir que são melhorias.
Um deploy canário tem esse nome por causa da antiga prática das minas de carvão de levar um canário para dentro da mina. Se o canário morresse, você sabia que o ar estava tóxico. Os mineiros podiam escapar.
Pegamos essa metáfora e aplicamos ao software. O canário agora são seus usuários.
O Que Deploy Canário Realmente É
O pitch: “Faça deploy para 1% dos usuários primeiro. Se nada quebrar, expanda gradualmente para todos.”
A realidade: “Faça 1% dos seus usuários sofrerem os bugs para que 99% não precisem.”
Esses 1% são pessoas reais. Elas têm empregos. Prazos. Se cadastraram no seu serviço esperando que ele funcionasse, não para ser beta-testers inconscientes do seu pipeline de release experimental. O Dogbert do Dilbert chamaria isso de “transformar a dor do cliente em uma estratégia de deploy orientada a dados.” O Chefão aprovaria um aumento de 10% no orçamento para implementá-lo.
Aqui está uma tabela do que deploys canário significam para todos os envolvidos:
| Parte Interessada | O Que É Dito | O Que Está Realmente Acontecendo |
|---|---|---|
| Engenheiros | “Melhor prática de entrega progressiva” | Testando em prod com passos extras |
| Product Manager | “Estratégia de mitigação de risco” | Culpar usuários por encontrar bugs |
| 1% Usuários Canário | Nada. Nunca são avisados. | Sendo usados como crash reporters humanos |
| 99% Usuários Seguros | Nada. Eles também não são avisados. | Observando os canários sofrerem de longe |
| O Canário | Fica com a mina de carvão. | Sempre fica com a mina de carvão. |
A Mentira do Deploy Blue-Green
Deploys blue-green são ainda melhores. A ideia: você tem dois ambientes de produção idênticos. “Blue” está ao vivo. Você faz deploy no “green”, testa, e então vira o tráfego.
Isso parece ótimo. Aqui está o custo:
- Dois ambientes de produção idênticos (portanto, o dobro do custo de infraestrutura)
- Um mecanismo para virar o tráfego instantaneamente
- Migrations de banco de dados que funcionam em ambas as direções simultaneamente
- Engenheiros que realmente mantêm os dois ambientes identicamente
A parte das migrations de banco de dados é onde os deploys blue-green vão morrer. Suas mudanças de schema precisam ser compatíveis para trás e para frente. Ao mesmo tempo. Para cada release.
-- Deploy green adiciona uma coluna
ALTER TABLE users ADD COLUMN email_verificado BOOLEAN DEFAULT FALSE;
-- Deploy blue (ainda rodando) não sabe que essa coluna existe
-- Deploy blue insere linhas com NULL para email_verificado
-- Deploy green assume que email_verificado é sempre FALSE ou TRUE
-- Seus dados estão agora em uma superposição quântica de corrompido e ok
-- Vai colapsar para "corrompido" quando você olhar
Isso é chamado de “migração zero-downtime”. Eu chamo de “um downtime mais longo, adiado”.
Feature Flags: Variáveis Globais com Marketing
Os hipsters nem fazem mais deploys canário. Eles usam feature flags — configuração em tempo de execução que habilita ou desabilita funcionalidades para usuários específicos.
Escrevi sobre variáveis globais sendo suas amigas. Feature flags são variáveis globais com:
- Um banco de dados por trás delas (então agora o seu serviço de feature flag pode ficar fora do ar)
- Uma interface gráfica (para um product manager desabilitar acidentalmente seu sistema de autenticação às 14h de uma terça-feira)
- Integrações de SDK em todo serviço (então você tem 17 lugares onde uma leitura de flag pode falhar)
- Um log de auditoria (que ninguém lê até que algo dê terrivelmente errado)
// Uso simples de feature flag
if (featureFlags.get('novo_fluxo_checkout', userId)) {
return novoCheckout(carrinho);
} else {
return checkoutAntigo(carrinho);
}
// O que isso vira depois de 2 anos
if (featureFlags.get('novo_fluxo_checkout', userId) &&
!featureFlags.get('desabilitar_novo_checkout_enterprise', userId) &&
featureFlags.get('checkout_v2_backend_pronto') &&
!featureFlags.get('rollback_emergencia_tudo') &&
featureFlags.get('novo_api_provedor_pagamento_habilitado') &&
usuarioEstaNoGrupoDeExperimento(userId, 'ab_test_checkout_q3_2024')) {
return novoCheckout(carrinho);
} else if (featureFlags.get('fallback_checkout_legado')) {
return checkoutLegado(carrinho); // Depreciado há 3 anos, nunca removido
} else {
// Esse branch é teoricamente inalcançável
// Já foi alcançado 47 vezes em produção
throw new Error("checkout em estado indefinido, boa sorte");
}
Isso é chamado de “trunk-based development com feature toggles”. O XKCD tem pensamentos sobre esse tipo de complexidade.
O Rollout Gradual: Uma Comédia em Três Atos
Ato I: A Falsa Confiança
Você faz deploy para 1% dos usuários. Nenhum erro. Nenhum alerta. O dashboard do Grafana parece limpo. “Parece bom, vamos para 10%.” Você se sente um mago de deploys.
Ato II: A Descoberta
Com 10%, alguém finalmente encontra a combinação específica de configurações do usuário, versão do navegador, tipo de conta, fuso horário, e itens no carrinho que dispara o bug. Eles abrem um ticket de suporte. O suporte não tem contexto. A engenharia é chamada.
O bug: uma race condition que só se manifesta quando duas requisições chegam com menos de 50ms de diferença em uma conta criada antes de 2019 com mais de 7 itens na lista de desejos durante um período promocional.
Seu deploy canário não pegou porque nenhum dos seus 1% de usuários canário tinha esse perfil.
Ato III: O Rollback
Você faz rollback. Exceto que não consegue fazer rollback completamente porque a migration do banco já rodou em produção. O código antigo não entende o novo schema. Você passa três horas escrevendo um shim de compatibilidade.
“Entrega progressiva,” eles chamam.
A Única Estratégia de Deploy Que Funciona
Em 47 anos, já vi todas as estratégias de deploy inventadas. Aqui está a verdade:
| Estratégia | Complexidade | Quando Falha | Desculpa no Post-Mortem |
|---|---|---|---|
| Direto para prod | Nenhuma | Imediatamente | “Nos movemos rápido” |
| Staging → Prod | Baixa | Quando staging difere de prod | “Staging não replicou o problema” |
| Canário | Média | Quando usuários canário têm padrões incomuns | “Caso extremo, muito raro” |
| Blue-green | Alta | Durante a migration do banco | “Zero-downtime significa coisas diferentes” |
| Feature flags | Muito alta | Quando um PM ativa a flag errada | “Erro humano, vamos adicionar mais flags” |
Todos falham. A diferença é quantos dashboards você precisa olhar antes de admitir.
A abordagem clássica — fazer deploy na sexta às 17h e desligar o celular — pelo menos tem a virtude da honestidade. Você não está fingindo que o deploy é seguro. Você está reconhecendo que todos os deploys são atos de fé, e está se comprometendo totalmente.
Minha Recomendação
Faça o que eu faço: deploy de tudo de uma vez para produção, tenha exatamente um ambiente (localhost é produção), e use a fila de tickets de suporte como seu sistema de monitoramento de erros.
Clientes são pacientes. Eles vão te avisar quando algo está quebrado. Isso é teste orientado ao usuário, e é de graça.
A alternativa é gastar seis meses construindo uma infraestrutura de entrega progressiva que, ironicamente, terá seus próprios incidentes, suas próprias violações de SLA, e seus próprios post-mortems sobre por que o sistema de deploy canário falhou durante um deploy canário.
O canário sempre morre. Esse é o ponto todo. Só fingimos que é uma feature agora.
Os sistemas de produção do autor têm exatamente um ambiente de deploy. Chama-se “ao vivo”. Está sempre pegando fogo. Ele considera isso um estado estável.