Uma implantação de produção do PostgreSQL falhou após um desenvolvedor adicionar um argumento opcional a uma função existente com CREATE OR REPLACE FUNCTION. A alteração deixou duas funções com o mesmo nome, fazendo com que o banco de dados retornasse “function is not unique” e a API emitisse um erro 400. O incidente mostra como um único erro de migração pode corromper silenciosamente um schema ativo e por que verificações apenas em nível de código não são suficientes.
O que deu errado
A equipe precisava estender uma stored procedure com um parâmetro extra e opcional. Eles executaram CREATE OR REPLACE FUNCTION …, assumindo que isso sobrescreveria a definição antiga. O PostgreSQL só substitui uma função quando a lista completa de argumentos coincide exatamente. Alterar a assinatura cria uma entrada de função totalmente nova, deixando a original intacta.
Como o novo argumento tinha um valor padrão, os chamadores que fornecessem o número antigo de argumentos poderiam corresponder a qualquer uma das definições. O PostgreSQL não conseguiu decidir qual delas invocar e lançou o erro “function is not unique”, que apareceu como uma resposta 400 da API.
O repositório de código mostrava uma única definição, e um script personalizado que escaneava a árvore de código-fonte não relatou duplicatas. A duplicata existia apenas no banco de dados, introduzida quando um arquivo de migração antigo foi reexecutado no servidor.
Por que a migração passou despercebida
A migração que adicionou o parâmetro opcional simplesmente executou CREATE OR REPLACE FUNCTION. Quando a migração foi executada uma segunda vez — talvez após um rollback ou durante uma implantação repetida — o banco de dados tratou o comando como “adicionar uma nova sobrecarga” em vez de “substituir a existente”. A migração não verificou o estado resultante, portanto, a duplicata persistiu sem ser notada.
O script que verificava o repositório examinava os arquivos de origem, não o schema ativo. Ele estava olhando pela porta da frente enquanto o bug entrava pela porta dos fundos.
Os riscos
Uma única função ambígua pode derrubar qualquer serviço que dependa dela. A correção envolveu reverter a transação se a contagem de funções estivesse incorreta e notificar o cache do schema para recarregar.
Como proteger as migrações
A equipe reconstruiu a migração com verificações explícitas, transformando-a em uma operação de autoafirmação:
- Inicie uma transação para que qualquer falha reverta toda a alteração.
- Remova a função antiga explicitamente antes de criar a nova versão, garantindo que exista apenas uma definição.
- Crie a nova função com a assinatura desejada.
- Conte as funções com o nome fornecido em
pg_cataloge verifique se a contagem é exatamente uma. - Reverta (Roll back) a transação se a contagem divergir, evitando que a duplicata persista.
- Notifique o cache do schema para recarregar, garantindo que as consultas subsequentes vejam a definição atualizada.
Ao perguntar ao banco de dados “quais funções estão presentes?” em vez de assumir que o código está correto, a migração torna-se confiável contra execuções repetidas, implantações parciais ou edições manuais.
Contra-argumento: conveniência vs. segurança
CREATE OR REPLACE FUNCTION é atraente porque permite que os desenvolvedores iterem rapidamente sem escrever comandos de drop separados. Em ambientes onde as migrações são executadas uma única vez e nunca mais, o atalho funciona bem. O risco aparece quando as migrações são repetidas — seja devido a pipelines de CI que resetam bancos de dados de teste, rollbacks automatizados ou reaplicações manuais em produção.
Lição aprendida
Alterar a assinatura de uma função com CREATE OR REPLACE não garante a substituição — o PostgreSQL criará silenciosamente uma sobrecarga se a lista de argumentos for diferente. Ambientes de produção que dependem de migrações devem verificar o schema resultante, não apenas o código-fonte. Incorporar drops explícitos, verificações transacionais e asserções pós-migração transforma um atalho conveniente em um processo confiável e repetível.
