Skip to content

Jalan Mundur ke Nol: Down Migration yang Tak Pernah Dijalankan

Adityo Guni Waluyo

Checkpoint v56 dibangun di atas teori yang salah. Dua bug di file down, error 1553 dan 1364, baru ketahuan saat down-to-0 dijalankan lagi.

Ringkasan

Ceritanya checkpoint versi 56 yang selama ini jadi pegangan ternyata cuma teori yang nggak pernah diuji. Pas down-to-0 dicoba, dua file down kena error: satu lupa kolom slug, satu lagi buang index yang masih ditumpang FK. Solusinya, balik urutan index, perbaiki insert, terus bikin test roundtrip biar rantai migration bisa dibuktikan bolak-balik.

Perintah down ke versi nol akhirnya saya jalankan lagi, pertama kali dalam hitungan bulan. Dugaan saya waktu itu optimistis: rantainya sehat sampai versi 56, tinggal membuka jalan di bawahnya. Kenyataannya rantai berhenti di tengah dengan dua error yang tidak ada hubungannya dengan alasan kenapa selama ini turunnya cuma sampai 56. Saya berdiri di depan terminal dengan satu kesadaran yang tidak nyaman: teori yang jadi dasar checkpoint ternyata tidak pernah diuji.

Checkpoint v56 dan Teori yang Salah

Aturan tidak tertulis di proyek: down-to-0 tidak boleh, karena versi 56 dianggap titik aman terakhir. Alasannya teori urutan ENUM, kira-kira begini: struktur ENUM di versi itu diduga tidak cocok dengan yang diharapkan server, jadi memaksa down lebih jauh diyakini berisiko merusak data. Tanpa pembuktian, teori itu jadi pegangan, dan checkpoint 56 dipakai sebagai jawaban final setiap kali ada yang ingin reset skema.

Eksekusi down-to-0 yang hari itu membongkar semuanya. Yang gagal justru dua langkah mekanis yang filenya sudah lama tidak disentuh:

  • 000051.down mencoba INSERT ulang lima kategori module='INFORMASI' tanpa kolom slug. Kolom itu NOT NULL dan tidak punya DEFAULT, sehingga MySQL melempar error 1364, Field 'slug' doesn't have a default value [5]. Penyebabnya murni kronologis: file down ditulis sebelum kolom slug lahir di migration berikutnya, dan sejak itu tidak pernah dijalankan lagi.
  • 000022.down melakukan DROP INDEX uq_views_entity_ip sebelum menambah penggantinya. Masalahnya, FK di kolom entity_id tabel entity_views menumpang pada index unik itu. MySQL mensyaratkan index untuk FK supaya pengecekan tidak jatuh ke table scan [1], jadi begitu satu-satunya index penopang mau dibuang, server menolak dengan error 1553, Cannot drop index ... needed in a foreign key constraint [4].

Dua Perbaikan, Satu Prinsip Urutan

Perbaikan keduanya sederhana, tapi prinsipnya bisa dipakai ulang: jangan pernah menghilangkan penopang sebelum penggantinya berdiri. Di 000022.down, urutan dibalik: ADD KEY idx_views_entity_ip (entity_id, ip_address) dulu, baru DROP INDEX uq_views_entity_ip. Begitu ada index lain yang bisa menopang FK, penghapusan index lama disetujui server. Di 000051.down, INSERT dibangun ulang dengan kolom slug dan sort_order yang nilainya diambil setia dari 000045.up, teks aslinya, bukan karangan: olahraga, budaya, promosi, wisata, umum. Fidelity ke nilai awal itu penting karena down migration adalah pernyataan bahwa kondisi lama bisa dipulihkan apa adanya, bukan versi yang mirip.

Yang mengganggu saya dari insiden ini: kedua file down itu tidak pernah salah secara sintaks, lolos review waktu dibuat, dan gagal hanya karena dunia bergerak. Kolom baru bermunculan, index berubah peran, dan mode SQL ketat yang dulu memaafkan INSERT kurang kolom kini menolaknya. File down yang tidak dijalankan membusuk secara diam-diam, sementara file up-nya terus diuji setiap kali ada environment baru. Membaca ulang dua file itu terasa seperti membaca surat dari masa lalu yang alamatnya sudah tidak ada.

Roundtrip Sebagai Satu-satunya Bukti

Solusi jangka panjangnya lebih dari sekadar catatan "hati-hati di versi sekian": test yang membuktikan keseluruhan rantai, head ke 0 lalu naik lagi ke head. Dokumentasi golang-migrate sendiri menegaskan "all migrations should be reversible" dan merekomendasikan down migration yang membersihkan state versi up-nya [7]. Test roundtrip migrations_test membandingkan snapshot skema sebelum dan sesudah perjalanan: setiap kolom dan setiap FK dibaca dari information_schema dengan urutan deterministik, lalu snapshot fresh-up versus pasca-roundtrip dibandingkan baris per baris. FK 000067 bahkan disentuh eksplisit: fk_reviews_user, fk_regsub_user, fk_report_user wajib terbaca RESTRICT, sementara otp_codes wajib tetap CASCADE. Tanpa itu, "bersih" hanya kesan; dengannya, drift sekecil apapun langsung kelihatan.

Checkpoint v56 kini resmi pensiun, dicatat sebagai keputusan owner. Rollback path yang tidak pernah dijalankan bukan jalan mundur, melainkan ilusinya saja. Di tempatnya berdiri jaminan yang bisa diukur: rantai migrasi yang boleh diturunkan ke nol kapan saja, karena buktinya adalah test yang jalan tiap kali rantai berubah, bukan teori.

Artikel terkait