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_catalog e 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.