Sebuah deployment PostgreSQL di lingkungan produksi mengalami crash setelah seorang pengembang menambahkan argumen opsional ke fungsi yang sudah ada menggunakan CREATE OR REPLACE FUNCTION. Perubahan tersebut menyisakan dua fungsi dengan nama yang sama, menyebabkan database mengembalikan pesan “function is not unique” dan API mengeluarkan error 400. Insiden ini menunjukkan bagaimana satu kesalahan migrasi dapat merusak skema live secara diam-diam dan mengapa pemeriksaan di tingkat kode saja tidaklah cukup.
Apa yang salah
Tim perlu memperluas sebuah stored procedure dengan parameter tambahan yang bersifat opsional. Mereka menjalankan CREATE OR REPLACE FUNCTION …, dengan asumsi bahwa perintah tersebut akan menimpa definisi yang lama. PostgreSQL hanya mengganti fungsi jika daftar argumen lengkapnya cocok secara persis. Mengubah signature akan membuat entri fungsi yang benar-benar baru, sementara fungsi yang asli tetap tidak tersentuh.
Karena argumen baru tersebut memiliki nilai default, pemanggil (caller) yang menyertakan jumlah argumen lama dapat cocok dengan salah satu dari kedua definisi tersebut. PostgreSQL tidak dapat memutuskan mana yang harus dipanggil dan akhirnya memunculkan error “function is not unique”, yang muncul sebagai respons 400 dari API.
Repositori kode hanya menunjukkan satu definisi, dan skrip khusus yang memindai source tree melaporkan tidak ada duplikasi. Duplikasi tersebut hanya ada di dalam database, yang muncul ketika file migrasi lama dijalankan ulang di server.
Mengapa migrasi tersebut lolos
Migrasi yang menambahkan parameter opsional tersebut hanya menjalankan CREATE OR REPLACE FUNCTION. Ketika migrasi dijalankan untuk kedua kalinya—mungkin setelah rollback atau selama deployment berulang—database memperlakukan perintah tersebut sebagai "tambahkan overload baru" alih-alih "ganti yang sudah ada". Migrasi tersebut tidak memverifikasi status akhirnya, sehingga duplikasi tetap ada tanpa disadari.
Skrip yang memeriksa repositori memeriksa file sumber, bukan skema yang sedang berjalan (live schema). Skrip tersebut memeriksa pintu depan, sementara bug masuk melalui pintu belakang.
Risiko yang dihadapi
Satu fungsi yang ambigu dapat melumpuhkan layanan apa pun yang bergantung padanya. Perbaikannya melibatkan pembatalan (rollback) transaksi jika jumlah fungsi salah dan memberitahu schema cache untuk memuat ulang.
Cara mengamankan migrasi
Tim membangun kembali migrasi tersebut dengan pemeriksaan eksplisit, mengubahnya menjadi operasi yang melakukan verifikasi mandiri (self-asserting operation):
- Mulai sebuah transaksi sehingga kegagalan apa pun akan membatalkan (rollback) seluruh perubahan.
- Hapus fungsi lama secara eksplisit sebelum membuat versi baru, untuk menjamin hanya ada satu definisi.
- Buat fungsi baru dengan signature yang diinginkan.
- Hitung jumlah fungsi dengan nama tersebut di
pg_catalogdan verifikasi bahwa jumlahnya tepat satu. - Lakukan rollback pada transaksi jika jumlahnya menyimpang, guna mencegah duplikasi tetap ada.
- Beritahu schema cache untuk memuat ulang, guna memastikan kueri berikutnya melihat definisi yang telah diperbarui.
Dengan bertanya kepada database “fungsi apa saja yang ada?” alih-alih berasumsi bahwa kode sudah benar, migrasi menjadi andal terhadap pengulangan eksekusi, deployment parsial, atau pengeditan manual.
Argumen tandingan: kenyamanan vs. keamanan
CREATE OR REPLACE FUNCTION sangat menarik karena memungkinkan pengembang untuk beriterasi dengan cepat tanpa harus menulis pernyataan drop terpisah. Di lingkungan di mana migrasi hanya dijalankan sekali dan tidak pernah dijalankan ulang, cara singkat ini berfungsi dengan baik. Risikonya muncul ketika migrasi dijalankan ulang—baik karena pipeline CI yang mereset database pengujian, rollback otomatis, atau penerapan ulang secara manual di produksi.
Kesimpulan
Mengubah signature fungsi dengan CREATE OR REPLACE tidak menjamin penggantian—PostgreSQL akan membuat overload secara diam-diam jika daftar argumen berbeda. Lingkungan produksi yang bergantung pada migrasi harus memverifikasi skema yang dihasilkan, bukan hanya kode sumbernya. Menyertakan perintah drop eksplisit, pemeriksaan transaksional, dan asersi pasca-migrasi akan mengubah cara singkat yang nyaman menjadi proses yang andal dan dapat diulang.
