Un deployment di produzione PostgreSQL è andato in crash dopo che uno sviluppatore ha aggiunto un argomento opzionale a una funzione esistente utilizzando CREATE OR REPLACE FUNCTION. La modifica ha lasciato due funzioni con lo stesso nome, causando il messaggio di errore “function is not unique” da parte del database e un errore 400 da parte dell'API. L'incidente dimostra come un singolo errore di migrazione possa corrompere silenziosamente uno schema live e perché i soli controlli a livello di codice non siano sufficienti.

Cosa è andato storto

Il team aveva la necessità di estendere una stored procedure con un parametro extra e opzionale. Hanno eseguito CREATE OR REPLACE FUNCTION …, presumendo che avrebbe sovrascritto la vecchia definizione. PostgreSQL sostituisce una funzione solo quando l'elenco completo degli argomenti corrisponde esattamente. Cambiare la firma crea una nuova voce per la funzione, lasciando intatta l'originale.

Poiché il nuovo argomento aveva un valore predefinito, le chiamate che fornivano il vecchio numero di argomenti potevano corrispondere a una qualsiasi delle due definizioni. PostgreSQL non è stato in grado di decidere quale invocare e ha restituito l'errore “function is not unique”, che si è manifestato come una risposta 400 dall'API.

Il repository del codice mostrava una singola definizione e uno script personalizzato che scansionava l'albero sorgente non riportava duplicati. Il duplicato esisteva solo nel database, introdotto quando un vecchio file di migrazione è stato rieseguito sul server.

Perché la migrazione è passata inosservata

La migrazione che ha aggiunto il parametro opzionale ha semplicemente eseguito CREATE OR REPLACE FUNCTION. Quando la migrazione è stata eseguita una seconda volta — forse dopo un rollback o durante un deployment ripetuto — il database ha trattato il comando come un "aggiungi un nuovo overload" piuttosto che come un "sostituisci l'esistente". La migrazione non ha verificato lo stato risultante, quindi il duplicato è persistito senza essere notato.

Lo script che controllava il repository esaminava i file sorgente, non lo schema live. Guardava dalla porta principale mentre il bug entrava dalla porta sul retro.

I rischi in gioco

Una singola funzione ambigua può mandare in crash qualsiasi servizio che si affidi ad essa. La soluzione ha comportato il rollback della transazione se il conteggio delle funzioni era errato e la notifica alla cache dello schema per ricaricarla.

Come proteggere le migrazioni

Il team ha ricostruito la migrazione con controlli espliciti, trasformandola in un'operazione di auto-verifica:

  • Avviare una transazione in modo che qualsiasi errore annulli l'intera modifica (rollback).
  • Eliminare esplicitamente la vecchia funzione prima di creare la nuova versione, garantendo che esista una sola definizione.
  • Creare la nuova funzione con la firma desiderata.
  • Contare le funzioni con quel nome in pg_catalog e verificare che il conteggio sia esattamente uno.
  • Effettuare il rollback della transazione se il conteggio differisce, impedendo la persistenza del duplicato.
  • Notificare la cache dello schema per ricaricarla, assicurando che le query successive vedano la definizione aggiornata.

Chiedendo al database “quali funzioni sono presenti?” invece di presumere che il codice sia corretto, la migrazione diventa affidabile contro esecuzioni ripetute, deployment parziali o modifiche manuali.

Controargomentazione: comodità vs sicurezza

CREATE OR REPLACE FUNCTION è attraente perché permette agli sviluppatori di iterare rapidamente senza dover scrivere istruzioni DROP separate. In ambienti in cui le migrazioni vengono eseguite una sola volta e mai più, questa scorciatoia funziona bene. Il rischio si presenta quando le migrazioni vengono riproposte — che sia a causa di pipeline CI che resettano i database di test, rollback automatizzati o riapplicazioni manuali in produzione.

In sintesi

Cambiare la firma di una funzione con CREATE OR REPLACE non garantisce la sostituzione: PostgreSQL creerà silenziosamente un overload se l'elenco degli argomenti è diverso. Gli ambienti di produzione che si affidano alle migrazioni devono verificare lo schema risultante, non solo il codice sorgente. Inserire DROP espliciti, controlli transazionali e asserzioni post-migrazione trasforma una scorciatoia comoda in un processo affidabile e ripetibile.