Ketika Skema Database Juga Butuh Version Control

Bayangkan ini: tim Anda memiliki pipeline CI/CD yang solid untuk kode aplikasi. Setiap pull request memicu pengujian otomatis, membangun image container, dan melakukan deploy ke staging. Kemudian tibalah deploy produksi. Pipeline berjalan, aplikasi mulai, dan langsung crash dengan error kolom tidak ditemukan. Seseorang lupa menjalankan migrasi database yang menambahkan kolom phone_number. Deploy gagal, pengguna melihat error, dan tim kebingungan mencari tahu apa yang salah.

Skenario ini terjadi di banyak tim setiap hari. Kode aplikasi di-versioning, diuji, dan di-deploy melalui pipeline. Namun perubahan skema database dianggap sebagai urusan belakangan, sesuatu yang dijalankan secara manual sebelum atau sesudah deploy. Kesenjangan antara deploy kode dan perubahan skema menciptakan celah di mana error bisa lolos.

Masalahnya: Bagaimana Pipeline Tahu Apa yang Sudah Dijalankan?

Ketika Anda memiliki direktori skrip migrasi seperti V001_create_users.sql, V002_add_phone.sql, dan V003_add_index.sql, pipeline perlu tahu mana yang sudah diterapkan ke database. Anda tidak bisa menjalankan semua file dari awal setiap kali deploy. Database produksi sudah memiliki data nyata. Menjalankan V001 lagi akan gagal karena tabel sudah ada, atau lebih buruk lagi, akan menghapus dan membuat ulang tabel sehingga menghancurkan data pelanggan.

Tanpa mekanisme pelacakan, tim terpaksa melakukan pengecekan manual. Seseorang login ke database, menjalankan \dt atau SHOW TABLES, dan mencoba mengingat apa yang sudah di-deploy minggu lalu. Atau mereka mengandalkan spreadsheet bersama yang tidak pernah diperbarui. Atau mereka hanya menjalankan migrasi dan berharap semuanya berjalan lancar.

Tidak ada satu pun pendekatan ini yang bisa diskalakan. Semuanya memperkenalkan human error, memperlambat deploy, dan menciptakan ketakutan di setiap perubahan database.

Solusinya: Migration Table di Database

Jawabannya ternyata sederhana: biarkan database melacak riwayat migrasinya sendiri. Buat tabel khusus, biasanya bernama schema_migrations atau migration_history, yang mencatat setiap skrip migrasi yang telah dijalankan.

Berikut cara kerjanya dalam praktik:

  1. Pertama kali alat migrasi dijalankan terhadap database kosong, ia membuat tabel migrasi.
  2. Setelah setiap skrip migrasi berhasil dijalankan, alat tersebut menyisipkan baris dengan nama skrip dan timestamp eksekusi.
  3. Pada deploy berikutnya, pipeline membaca tabel migrasi, membandingkannya dengan daftar file migrasi yang tersedia, dan hanya menjalankan skrip yang belum tercatat.

Misalnya, setelah V001_create_users.sql dijalankan, tabel migrasi berisi satu baris: V001_create_users.sql. Ketika deploy berikutnya menyertakan V002_add_phone.sql, pipeline memeriksa tabel, melihat bahwa V002 tidak ada, dan menjalankannya. Setelah berhasil, ia menambahkan baris baru. Database itu sendiri menjadi sumber kebenaran tunggal untuk versi skema yang sedang berjalan.

Mengapa Ini Penting untuk Pipeline Anda

Mekanisme ini disebut version locking. Database memegang catatan otoritatif tentang statusnya sendiri. Tidak perlu file konfigurasi terpisah, variabel lingkungan yang mungkin tidak sinkron, atau daftar periksa manual yang lupa diperbarui.

Untuk pipeline CI/CD, ini sangat kritis. Pipeline sekarang dapat membuat keputusan objektif: "Berdasarkan apa yang database katakan, saya perlu menjalankan tiga file migrasi ini." Tanpa tebakan, tanpa pengecekan manual, tanpa rasa takut menjalankan migrasi yang sama dua kali.

Diagram urutan berikut mengilustrasikan alur ini:

sequenceDiagram participant Pipeline as CI/CD Pipeline participant Scripts as Migration Scripts Directory participant Table as Database Migration Table participant Schema as Database Schema Pipeline->>Table: Query applied migrations Table-->>Pipeline: Return list (e.g., V001, V002) Pipeline->>Scripts: Compare with available scripts Scripts-->>Pipeline: New scripts (e.g., V003) Pipeline->>Schema: Run V003_add_index.sql Schema-->>Pipeline: Success Pipeline->>Table: Insert V003 record Pipeline->>Schema: Application starts with updated schema

Berbagai alat migrasi mengimplementasikan ini dengan cara yang sedikit berbeda. Beberapa mencatat checksum dari setiap file migrasi untuk mendeteksi jika seseorang memodifikasi skrip yang sudah dijalankan. Yang lain menggunakan nomor versi berurutan alih-alih nama file. Beberapa alat menyimpan riwayat migrasi di skema atau database terpisah. Namun prinsip intinya tetap sama: database melacak riwayatnya sendiri, dan pipeline membaca riwayat itu untuk menentukan langkah selanjutnya.

Bootstrapping: Migrasi Pertama

Ada masalah ayam-dan-telur di sini. Tabel migrasi itu sendiri harus ada sebelum migrasi lain dapat dicatat. Bagaimana cara membuatnya?

Sebagian besar alat migrasi menangani ini secara otomatis. Ketika Anda menjalankan alat terhadap database kosong untuk pertama kalinya, ia membuat tabel migrasi sebagai bagian dari proses bootstrap-nya. Beberapa alat bahkan mencatat tindakan bootstrap ini sebagai entri pertama dalam riwayat migrasi.

Jika Anda mengadopsi skrip migrasi untuk database yang sudah ada yang sudah memiliki tabel dan data, Anda memerlukan pendekatan yang berbeda. Di sinilah baseline migration berperan. Alih-alih mencoba membuat ulang setiap perubahan historis, Anda membuat satu skrip migrasi yang menangkap status skema database saat ini. Anda menandai ini sebagai baseline, dan alat migrasi mencatatnya sebagai sudah diterapkan. Mulai saat itu, Anda hanya menambahkan skrip migrasi baru untuk perubahan ke depan.

Baseline migration adalah solusi pragmatis. Ini mengakui bahwa Anda tidak dapat menulis ulang sejarah, tetapi Anda dapat mulai melacak perubahan mulai hari ini. Alternatifnya adalah dengan merekayasa balik setiap perubahan skema yang pernah dibuat, yang tidak praktis bagi sebagian besar tim.

Apa yang Tidak Dipecahkan oleh Migration Table

Migration table memecahkan satu masalah spesifik: mengetahui skrip mana yang sudah dijalankan. Ini memberikan cara yang andal bagi pipeline untuk menentukan versi skema saat ini dan menerapkan perubahan yang tertunda.

Namun ini tidak menyelesaikan semuanya. Migration table bekerja dengan baik untuk perubahan aditif seperti menambahkan tabel, kolom, atau indeks. Perubahan ini tidak merusak kode aplikasi yang sudah ada yang ditulis untuk skema lama. Masalah dimulai ketika Anda perlu menghapus atau mengganti nama kolom, mengubah tipe data, atau merestrukturisasi tabel. Perubahan destruktif atau transformatif ini dapat merusak aplikasi yang sedang berjalan, menyebabkan downtime, atau merusak data.

Migration table memberi tahu Anda apa yang telah dijalankan, tetapi tidak memberi tahu Anda apakah aplikasi yang berjalan kompatibel dengan skema baru. Itu membutuhkan serangkaian praktik berbeda seputar migrasi yang kompatibel mundur, peluncuran bertahap, dan koordinasi yang cermat antara deploy kode dan perubahan skema.

Daftar Periksa Praktis untuk Pelacakan Migrasi

  • Pilih alat migrasi yang mendukung pelacakan otomatis melalui migration table.
  • Pastikan migration table dibuat selama deploy pertama, bukan secara manual.
  • Jangan pernah memodifikasi skrip migrasi yang sudah diterapkan ke produksi.
  • Jika mengadopsi migrasi untuk database yang sudah ada, buat baseline migration terlebih dahulu.
  • Sertakan eksekusi migrasi sebagai langkah dalam pipeline deploy Anda, bukan proses manual.
  • Uji migrasi di lingkungan staging yang mencerminkan volume data produksi.

Kesimpulan Konkret

Skema database Anda sama pentingnya dengan kode aplikasi, dan ia berhak mendapatkan level version control dan otomatisasi yang sama. Migration table memberikan pipeline Anda cara yang andal dan berbasis database untuk mengetahui apa yang telah diterapkan dan apa yang masih perlu dijalankan. Tanpa itu, Anda hanya menebak. Dengan itu, Anda memiliki sumber kebenaran tunggal yang menghilangkan penyebab paling umum kegagalan deploy terkait perubahan database. Mulailah melacak versi skema Anda hari ini, dan diri Anda di masa depan akan berterima kasih saat deploy berikutnya.