01
A migração de 40 ms que derrubou a API por quase seis minutos
O deploy rodava a migração antes de subir a nova versão, como manda o roteiro. Naquele dia, um analista tinha disparado um relatório de BI que abre uma transação, lê a tabela orders e passa minutos agregando. Essa transação segura um ACCESS SHARE em orders, um lock fraco que ninguém percebe. Quando a migração pediu o ACCESS EXCLUSIVE que o ALTER TABLE exige, ela não pôde ser atendida e entrou na fila de espera do lock.
Até aqui o dano seria só um deploy lento. O problema é o que acontece com as consultas seguintes: um SELECT comum da API pede ACCESS SHARE, que não conflita com o ACCESS SHARE do relatório, mas conflita com o ACCESS EXCLUSIVE que está esperando na frente. O Postgres respeita a ordem da fila, então a API passa a esperar atrás da migração. Em 12 segundos as 50 conexões do pool estavam ocupadas esperando, e o serviço ficou mudo até o relatório terminar.
t=0 relatorio de BI abre transacao e le orders (ACCESS SHARE), vai durar 5 min 40 s t=10 s migracao: ALTER TABLE orders ADD COLUMN ... pede ACCESS EXCLUSIVE -> espera o relatorio t=10 s API: SELECT em orders pede ACCESS SHARE -> entra na fila ATRAS do ALTER t=12 s pool de 50 conexoes inteiro preso na fila, health check ainda responde 200 t=15 s usuarios veem timeout; novas requisicoes seguem chegando t=5m40 relatorio termina -> ALTER pega o lock, roda em 40 ms, solta -> a fila drena Com lock_timeout = 2 s: t=12 s ALTER desiste (55P03), libera a fila; API volta em milissegundos t=14 s migracao tenta de novo com backoff, ate o relatorio terminar
Dois sinais enganaram o time. O health check não toca a tabela orders e continuou verde, então o balanceador manteve a instância no ar. E o gráfico de CPU do banco estava quase zerado, porque nada estava executando: tudo estava esperando. Um incidente de lock não parece um incidente de banco nos painéis comuns.