Ein PostgreSQL-Produktionsdeployment stürzte ab, nachdem ein Entwickler ein optionales Argument zu einer bestehenden Funktion mit CREATE OR REPLACE FUNCTION hinzugefügt hatte. Die Änderung hinterließ zwei Funktionen mit demselben Namen, was dazu führte, dass die Datenbank die Fehlermeldung „function is not unique“ zurückgab und die API einen 400-Fehler ausgab. Der Vorfall zeigt, wie ein einziger Migrationsfehler ein Live-Schema stillschweigend korrumpieren kann und warum Prüfungen auf Code-Ebene allein nicht ausreichen.
Was schiefgelaufen ist
Das Team musste eine gespeicherte Prozedur um einen zusätzlichen, optionalen Parameter erweitern. Sie führten CREATE OR REPLACE FUNCTION … aus, in der Annahme, dass dies die alte Definition überschreiben würde. PostgreSQL ersetzt eine Funktion nur dann, wenn die vollständige Argumentliste exakt übereinstimmt. Das Ändern der Signatur erstellt einen völlig neuen Funktionseintrag, während die ursprüngliche unverändert bleibt.
Da das neue Argument einen Standardwert hatte, konnten Aufrufer, die die alte Anzahl an Argumenten lieferten, mit beiden Definitionen übereinstimmen. PostgreSQL konnte nicht entscheiden, welche Funktion aufgerufen werden soll, und warf den Fehler „function is not unique“, der als 400er-Antwort der API erschien.
Das Code-Repository zeigte eine einzige Definition, und ein benutzerdefiniertes Skript, das den Source-Tree scannte, meldete keine Duplikate. Das Duplikat existierte nur in der Datenbank und wurde eingeführt, als eine alte Migrationsdatei auf dem Server erneut ausgeführt wurde.
Warum die Migration durchrutschte
Die Migration, die den optionalen Parameter hinzufügte, führte einfach CREATE OR REPLACE FUNCTION aus. Als die Migration ein zweites Mal lief – vielleicht nach einem Rollback oder während eines wiederholten Deployments – behandelte die Datenbank den Befehl als „füge eine neue Überladung hinzu“ statt als „ersetze die vorhandene“. Die Migration überprüfte den resultierenden Zustand nicht, sodass das Duplikat unbemerkt bestehen blieb.
Das Skript, das das Repository prüfte, untersuchte Quelldateien, nicht das Live-Schema. Es schaute zur Vordertür, während der Bug durch die Hintertür eindrang.
Der Ernstfall
Eine einzige mehrdeutige Funktion kann jeden Dienst lahmlegen, der auf sie angewiesen ist. Die Behebung bestand darin, die Transaktion zurückzurollen, falls die Anzahl der Funktionen falsch war, und den Schema-Cache zur Neuladung aufzufordern.
So sichern Sie Migrationen ab
Das Team baute die Migration mit expliziten Prüfungen neu auf und machte sie zu einer selbstvalidierenden Operation:
- Starten Sie eine Transaktion, damit bei einem Fehler die gesamte Änderung zurückgerollt wird.
- Löschen Sie die alte Funktion explizit, bevor Sie die neue Version erstellen, um zu garantieren, dass nur eine Definition existiert.
- Erstellen Sie die neue Funktion mit der gewünschten Signatur.
- Zählen Sie die Funktionen mit dem angegebenen Namen in
pg_catalogund verifizieren Sie, dass die Anzahl genau eins beträgt. - Rollen Sie die Transaktion zurück, falls die Anzahl abweicht, um zu verhindern, dass das Duplikat bestehen bleibt.
- Benachrichtigen Sie den Schema-Cache, um ihn zur Neuladung aufzufordern, damit nachfolgende Abfragen die aktualisierte Definition sehen.
Indem man die Datenbank fragt „Welche Funktionen sind vorhanden?“, anstatt vorauszusetzen, dass der Code korrekt ist, wird die Migration gegenüber wiederholten Durchläufen, teilweisen Deployments oder manuellen Änderungen zuverlässig.
Gegenargument: Komfort vs. Sicherheit
CREATE OR REPLACE FUNCTION ist attraktiv, weil es Entwicklern ermöglicht, schnell zu iterieren, ohne separate DROP-Anweisungen schreiben zu müssen. In Umgebungen, in denen Migrationen nur einmal laufen und nie wiederholt werden, funktioniert die Abkürzung einwandfrei. Das Risiko entsteht, wenn Migrationen erneut ausgeführt werden – sei es durch CI-Pipelines, die Testdatenbanken zurücksetzen, automatisierte Rollbacks oder manuelle erneute Anwendungen in der Produktion.
Fazit
Das Ändern der Signatur einer Funktion mit CREATE OR REPLACE garantiert keine Ersetzung – PostgreSQL erstellt stillschweigend eine Überladung, wenn die Argumentliste abweicht. Produktionsumgebungen, die auf Migrationen angewiesen sind, müssen das resultierende Schema verifizieren, nicht nur den Quellcode. Das Einbetten von expliziten DROP-Befehlen, transaktionalen Prüfungen und Assertions nach der Migration verwandelt eine bequeme Abkürzung in einen zuverlässigen, wiederholbaren Prozess.
