João Pedro

Command Palette

Search for a command to run...

Command Palette

Search for a command to run...

Blog

O Dia que Quebrei Produção, E o que Aprendi Sobre Culpa, Postmortem e Confiança

Rodei um UPDATE sem WHERE no banco do Axxos CRM e tirei meia tarde de venda do ar. O que aconteceu, o que fiz nas próximas duas horas e o que mudou em mim depois.

Tela de terminal com prompt de psql exibindo a mensagem UPDATE 8431, indicando uma atualização que afetou milhares de linhas

Era uma terça-feira, três e meia da tarde. Eu estava resolvendo um chamado simples: corrigir o status de um cliente específico no Axxos CRM. Abri o psql na conexão de produção, escrevi o UPDATE, conferi a query, dei enter. Quando o psql respondeu UPDATE 8431, levei uns dois segundos para entender o que tinha acontecido.

Tinha esquecido o WHERE. Acabei de marcar todos os clientes ativos do nosso maior tenant como inadimplentes. Em meia tarde de trabalho, esse erro custou paradas em vendas, ligações de cliente furioso e a sensação física de querer afundar no chão. Esse post é sobre o que fiz nas duas horas seguintes, o postmortem que escrevi depois, e o que esse dia me ensinou sobre culpa e sobre time.

Os primeiros cinco minutos: a parte mais difícil

A primeira reação foi paralisia. O cérebro empacou em "isso não pode estar acontecendo" por uns trinta segundos que pareceram um minuto. Foi o instinto mais inútil de todos. O cliente continuava com dado errado a cada segundo que eu ficava parado.

A segunda reação foi quase pior: a vontade de tentar resolver sozinho antes que alguém soubesse. Reescrever um UPDATE com base no que eu lembrava do estado anterior, torcer para acertar, e depois fingir que nada aconteceu. Isso é o tipo de impulso que transforma um incidente de meia hora em uma saga de dois dias, porque você inevitavelmente erra de novo, e agora o estado está duplamente corrompido e ninguém sabe.

Eu fiz a coisa que tinham me ensinado: avisei. Mandei mensagem no canal de incidentes em texto cru, sem ornamento: "Acabei de rodar UPDATE sem WHERE na tabela clientes. Estimo 8 mil linhas afetadas. Vou começar a investigar agora." Mandar essa mensagem foi a coisa mais incômoda que fiz no ano. Mas foi a única que abriu o caminho para o resto.

Em segundos, dois colegas saíram do que estavam fazendo e entraram em call. Sem julgamento, sem perguntar como eu fiz isso. Só "como a gente recupera, o que você precisa de mim". Foi a primeira vez que entendi, com o corpo, o que significa "psychological safety". Não é um pôster na parede de RH. É a velocidade com que a sua equipe consegue te ajudar quando você precisa.

Recuperação: o que salvou a tarde

A boa notícia era que tínhamos backup. A melhor notícia era que tínhamos pg_dump lógico diário e WAL archiving contínuo no RDS, o que me deu point-in-time recovery. A coisa simples que muita gente esquece é que ter backup não é a mesma coisa que ter restore testado, e eu já tinha escrito sobre isso meses antes, com o conforto de quem nunca tinha precisado.

A estratégia que combinamos em call:

-- 1. Subir uma cópia do banco em PITR para 15:25 (5 minutos antes do incidente).
--    Isso vira o axxos-crm-recovery, uma instância RDS temporária.
-- 2. Conectar uma sessão psql nessa cópia, exportar só as linhas afetadas:
 
COPY (
  SELECT id, status, atualizado_em
  FROM clientes
  WHERE tenant_id = 'TENANT_AFETADO'
) TO STDOUT WITH (FORMAT csv, HEADER true);
 
-- 3. Em produção, rodar o UPDATE com JOIN no CSV reimportado em uma tabela temp:
 
CREATE TEMP TABLE backup_clientes (
  id uuid,
  status text,
  atualizado_em timestamptz
);
 
\copy backup_clientes FROM 'backup_clientes.csv' WITH (FORMAT csv, HEADER true);
 
UPDATE clientes c
SET status = b.status
FROM backup_clientes b
WHERE c.id = b.id
  AND c.tenant_id = 'TENANT_AFETADO'
  AND c.status = 'INADIMPLENTE';  -- proteção: só reverte o que eu quebrei

O AND c.status = 'INADIMPLENTE' foi a paranóia que ficou comigo dali em diante. Mesmo que minha lógica estivesse certa, eu não ia rodar nenhum UPDATE em produção naquele dia sem uma cláusula extra que limitasse o pior cenário. Se algo desse errado, o pior caso era não reverter nada, não causar dano novo.

Tempo total do incidente: 1h47. Tempo em que o cliente teve dado errado visível na UI: 1h12 (depois disso o banco estava certo, mas algumas caches no front demoraram para invalidar). Foi caro. Não foi catastrófico.

Stack técnica

01

RDS Point-in-Time Recovery

O que salvou a tarde. Permite subir uma cópia do banco em qualquer segundo da janela de retenção. Custou 40 minutos para provisionar e foi a diferença entre incidente e desastre.

02

Canal #incidentes no Slack

Acordo do time: qualquer um pode declarar incidente em texto cru, sem template. A velocidade de declarar vence a precisão do que se declara.

03

Doc de postmortem (Notion)

Template fixo: cronologia, causa raiz, impacto, ações. Sem nome de pessoa em lugar nenhum. Aprendizado coletivo, não vergonha individual.

04

Proteções no psql (depois do incidente)

~/.psqlrc com SET safe_update_threshold ou wrapper que recusa UPDATE/DELETE sem WHERE em conexões marcadas como prod.

O postmortem: por que escrever sem nome de pessoa funciona

Dois dias depois, escrevi o postmortem. A primeira versão saiu como quase todo postmortem escrito por quem causou o incidente: cheio de "eu errei", "eu deveria", "eu não vi". Meu tech lead leu, devolveu, e disse uma coisa que mudou como eu enxergo erro em engenharia: "tira o seu nome do documento e veja se o postmortem ainda faz sentido".

Tirei. Não fazia. O que sobrava era "alguém com acesso de escrita em produção rodou um UPDATE sem WHERE". A pergunta certa não era "como o João pode ser mais cuidadoso?". Era "por que o sistema permite que uma pessoa execute isso direto na prod sem nenhuma barreira?". A resposta dessa segunda pergunta gerou três mudanças de processo que sobreviveram a mim.

Primeiro: ninguém mais tem acesso de escrita direta em prod via psql. Acessos de leitura sim, escrita é via PR no repositório de migrations e aplicada pelo CI. Para casos extremos onde precisa rodar manualmente, é com pair em call, gravando a tela.

Segundo: ~/.psqlrc no servidor bastion tem \set PROMPT1 '%[%033[1;31m%]%/@%m (PROD)>%[%033[0m%] ' e uma flag que exige confirmação dupla para qualquer comando que comece com UPDATE ou DELETE. Visual e processual. A flag não é infalível, mas adiciona um momento de pausa.

Terceiro: o template de postmortem mudou. Não tem mais "responsável" como campo. Tem "sistema que permitiu". Tem "barreira que faltou". Tem "ação para evitar para qualquer pessoa". O nome do humano aparece exatamente uma vez, no campo "quem escreveu o postmortem", porque o postmortem é trabalho e merece crédito.

O que mudou em mim

Por umas três semanas depois eu trabalhei com medo. Cada query em produção eu lia três vezes. Cada deploy parecia perigoso. Foi um período difícil, e eu achei que não ia passar. Passou.

O que passou junto com o medo foi uma certa arrogância silenciosa que eu nem sabia que carregava. Antes, eu lia postmortem de incidente dos outros e pensava, por trás, "eu não cometeria esse erro". Hoje eu leio e penso "esse erro também está esperando por mim". É uma postura mais humilde e mais útil. Faz você ler com atenção em vez de ler com julgamento.

A outra coisa que mudou foi a relação com o time. Quem te ajuda no pior dia da sua vida profissional vira referência de outro tipo. Eu confio diferente nas pessoas que estavam na call comigo naquela terça-feira. E quando alguém do meu time quebra alguma coisa, eu sou a primeira pessoa a entrar em call sem perguntar como aconteceu. Porque eu lembro.

Impacto em números

Tempo de incidente
107min

Da execução do UPDATE à confirmação de dado correto

Mudanças de processo permanentes
3

Removeram a classe inteira de incidente

Postmortems escritos sem nome de pessoa
100%

Padrão do time desde então

O que eu diria pra quem ainda vai passar por isso

Você vai quebrar produção. Talvez seja um UPDATE sem WHERE, talvez seja uma migration que não testou, talvez seja um deploy de sexta. Não importa quanto cuidado você tenha. Quem trabalha o suficiente em sistemas suficientemente grandes eventualmente vai ser a pessoa que apertou o botão errado. Esse é um fato sobre engenharia, não sobre você.

O que define quem você vai ser depois é o que você fizer nas primeiras horas e nas primeiras semanas. Avisar rápido, sem maquiar. Aceitar ajuda sem fingir que dá conta sozinho. Escrever o postmortem como se fosse de outra pessoa. Implementar a barreira que faltou, não a regra de "ter mais cuidado". E perdoar a si mesmo no ritmo certo, nem rápido demais a ponto de não aprender, nem devagar demais a ponto de virar trauma.

Engenharia decente é construída em cima de erros honestos. Postmortems sem nome são o jeito que a gente transforma o pior dia de uma pessoa em melhoria que protege todo mundo. E é o tipo de coisa que só funciona se você acreditar mesmo. Eu acredito agora.