Schema Migration Patterns — Database

Apa itu Schema Migration? Schema migration adalah cara mengelola perubahan struktur database secara terstruktur dan version-controlled. Setiap perubahan…

Migration adalah file yang mencatat satu perubahan struktur database (tambah tabel, tambah kolom, hapus kolom), dijalankan berurutan, dan dicatat sudah jalan atau belum. Hasilnya, laptopmu, laptop temanmu, dan server production punya struktur yang sama persis. Pola expand/contract adalah cara mengubah struktur itu tanpa bikin aplikasi error. Bayangin renovasi dapur restoran yang tetap buka: kamu gak membongkar dapur lama sebelum dapur baru siap. Bangun dapur baru di sebelahnya (expand), pindahkan koki satu per satu, baru bongkar yang lama (contract).

🧪 Tebakan cepat: di tabel export_products Kedai Senja, kolom price_usd (REAL) mau diganti price_cents (INTEGER), karena di pelajaran Tipe Data kita lihat uang sebagai REAL bisa melenceng. Satu migration langsung menambah price_cents, mengisinya, lalu menghapus price_usd. Kode baru baru selesai di-deploy 2 menit kemudian. Apa yang dialami pembeli selama 2 menit itu? Tebak dulu, jawabannya ada di bagian "Jalan pintas yang bikin error".

Satu file migration di Laravel

Bikin file-nya dengan artisan:

php artisan make:migration add_price_cents_to_export_products_table

Laravel membuat file bernama tanggal dan jam, misalnya 2026_10_08_090000_add_price_cents_to_export_products_table.php, lalu kamu isi:

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('export_products', function (Blueprint $table) {
            $table->integer('price_cents')->nullable();
        });
    }

    public function down(): void
    {
        Schema::table('export_products', function (Blueprint $table) {
            $table->dropColumn('price_cents');
        });
    }
};

File yang sudah tercatat di tabel migrations gak akan dijalankan lagi. Jadi kalau kamu mengedit migration lama yang sudah jalan di production, server itu menjawab Nothing to migrate. dan perubahanmu gak pernah sampai. Untuk perubahan berikutnya, selalu bikin migration baru.

Seberapa berisiko sebuah perubahan

Perubahan Risiko Kenapa
add column (nullable), add index low Kode lama gak peduli kolom atau index baru
rename column, change type medium Kode lama masih memakai nama atau tipe lama, dan data bisa gagal dikonversi
drop column, drop table high Datanya hilang permanen, dan kode yang masih memakainya langsung error

Perubahan additive (menambah) aman. Perubahan destructive (menghapus atau mengganti) butuh langkah yang hati-hati. Satu catatan: menambah index di tabel jutaan baris bisa mengunci tabel selama index dibuat, makanya PostgreSQL punya CREATE INDEX CONCURRENTLY.

Jalan pintas yang bikin error

Ini yang terjadi di Lab kalau DROP COLUMN jalan saat aplikasi lama masih membaca price_usd:

ALTER TABLE export_products ADD COLUMN price_cents INTEGER;
UPDATE export_products SET price_cents = CAST(ROUND(price_usd * 100) AS INTEGER);
ALTER TABLE export_products DROP COLUMN price_usd;

-- aplikasi lama membuka katalog:
SELECT name, price_usd FROM export_products;
-- no such column: price_usd

-- aplikasi lama menambah produk:
INSERT INTO export_products (name, price_usd) VALUES ('Jahe Merah', 2.75);
-- table export_products has no column named price_usd

Selama 2 menit sebelum kode baru selesai di-deploy, katalog dan halaman admin error. Itu jawaban tebakannya. Migration dan deploy kode gak pernah terjadi di detik yang sama, dan saat deploy bertahap, server lama dan baru jalan bersamaan. Jadi di setiap detik, struktur database harus cocok dengan semua versi kode yang sedang jalan.

Expand/contract, langkah demi langkah

Tabel awalnya export_products (id, name, price_usd) berisi Kopi Gayo 250 g $4.35, Teh Tarik Sachet $1.10, Gula Aren 500 g $2.20. Lima langkah ini sama persis dengan tombol di Lab:

Langkah Yang dijalankan Aplikasi yang jalan
1. Expand ALTER TABLE export_products ADD COLUMN price_cents INTEGER; v1: baca dan tulis price_usd
2. Tulis dua kolom deploy v2 v2: baca price_usd, tulis dua-duanya
3. Backfill UPDATE export_products SET price_cents = CAST(ROUND(price_usd * 100) AS INTEGER) WHERE price_cents IS NULL; v2
4. Pindah baca deploy v3 v3: baca dan tulis price_cents saja
5. Contract ALTER TABLE export_products DROP COLUMN price_usd; v3

Di setiap langkah, semua request di Lab berhasil. Memang lebih panjang daripada jalan pintas, tapi gak ada satu detik pun error.

📌 Di tabel besar production

⚠️ Jebakan yang sering ketemu

TL;DR: Migration adalah file perubahan struktur database yang dibuat dengan php artisan make:migration, dijalankan dengan php artisan migrate, dicatat di tabel migrations, dan gak pernah dijalankan dua kali, jadi perubahan baru selalu butuh migration baru. Additive (add column, add index) risikonya low, rename atau change type medium, drop column atau drop table high. Untuk mengganti kolom tanpa error, pakai expand/contract: tambah kolom nullable, tulis ke dua kolom, backfill, pindah baca, baru hapus kolom lama.

Yang akan kamu pelajari