Jul 24, 2025

DB Migrations Tanpa Drama (Two-Phase Changes)

“Cuma migration kecil kok” itu kalimat yang sering jadi opening scene buat incident. Di production, migration bukan sekadar SQL, itu operasi bedah sambil pasien masih jalan-jalan.

Maarif Alawi

Full-Stack Web App Developer

Satu ALTER TABLE bisa bikin API error, halaman blank, atau queue macet, walau code kamu “gak berubah banyak”. Dan yang bikin nyebelin: bug-nya sering muncul setengah jam setelah deploy, pas semua orang udah merasa aman. Dalam 3 tahun dan ~13 project shipped (Next.js + Supabase Postgres + Vercel + Sentry), cara paling waras adalah: two-phase changes.

Cerita singkat: missing column bikin error berantai

Pernah ada migration yang nambah kolom baru, lalu code langsung pakai kolom itu seolah semua data udah punya value. Hasilnya: endpoint tertentu meledak dengan error “column does not exist” atau value null yang gak di-handle, tergantung instance mana yang kebetulan kena duluan.

Yang bikin lucu (dan pahit): di staging aman, karena datanya baru dan bersih. Di production, ada data lama dengan format beda dan edge case yang gak kepikiran.

Sentry jadi penyelamat karena pattern error-nya jelas dan cepat kebaca. Lesson-nya: migration aman itu bukan “SQL bener”, tapi “rollout bener”.

Prinsip 3 langkah: Add → Backfill → Switch

Two-phase itu cara mikir: jangan memaksa semua berubah sekaligus. Kamu bikin perubahan yang kompatibel dengan schema lama dan baru dalam satu window rilis.

Step 1, Add: tambah kolom/tabel baru yang aman (nullable, no strict constraint dulu). Code di-deploy dalam mode “dual-read / dual-write” kalau perlu.

Step 2, Backfill: isi data lama pelan-pelan, jangan sekaligus nge-lock table. Kalau datanya besar, lakukan batch, off-peak, dan monitor.

Step 3, Switch: setelah data beres, baru ganti logic supaya pakai field baru sebagai source of truth. Baru setelah itu kamu boleh cleanup kolom lama (di rilis berikutnya), bukan di menit yang sama.

Contoh simpel: mau “rename column” full_name jadi name. Jangan rename langsung. Tambah name, backfill dari full_name, deploy code yang baca COALESCE(name, full_name), switch write ke name, baru nanti hapus full_name.

Order rilis yang bener

Urutan aman itu ngebunuh 80% drama:

  1. Deploy code yang support dua schema (tahan null, fallback, jangan hard fail).

  2. Jalankan migration yang additive (tambah kolom/tabel/index yang aman).

  3. Backfill data, ideally batch + monitor (Sentry + logs).

  4. Switch logic di code (read/write ke field baru).

  5. Cleanup schema lama di rilis berikutnya, dengan rollback plan tetap ada.

Kalau kamu kebalik urutan (migrate dulu, code belum siap), kamu lagi main Russian roulette tapi versi database.

Anti-patterns migration yang bikin down

  • Rename/drop column langsung di jam kerja, lalu code lama masih jalan.
    Solusinya: two-phase, keep backward compatibility dulu.

  • Tambah NOT NULL tanpa default/backfill.
    Solusinya: tambah nullable, backfill, baru enforce constraint belakangan.

  • Long-running migration pas traffic rame.
    Solusinya: schedule off-peak, pakai batch, dan hindari table rewrite yang berat.

  • Gak ada rollback plan, cuma “semoga aman”.
    Solusinya: siapin rollback path (revert code, disable feature, atau stop switch) sebelum eksekusi.

Kode: SQL migration aman (Postgres/Supabase)

-- 001_add_name_two_phase.sql
-- Goal: introduce "name" safely without breaking existing reads/writes.

BEGIN;

-- 1) ADD: new column, nullable first
ALTER TABLE public.profiles
  ADD COLUMN IF NOT EXISTS name text;

COMMIT;

-- 2) BACKFILL: do it in small batches (repeat until 0 rows affected)
-- (In practice, run this statement multiple times via a job/cron)
UPDATE public.profiles p
SET name = p.full_name
WHERE p.name IS NULL
  AND p.full_name IS NOT NULL
  AND p.id IN (
    SELECT id FROM public.profiles
    WHERE name IS NULL AND full_name IS NOT NULL
    ORDER BY id
    LIMIT 500
  );

-- 3) CONSTRAINT (optional, only after backfill is complete)
-- Add constraint later, not in the first deploy window.
-- ALTER TABLE public.profiles
--   ALTER COLUMN name SET NOT NULL;

-- Optional: index for new queries (use CONCURRENTLY outside transaction)
-- CREATE INDEX CONCURRENTLY IF NOT EXISTS profiles_tenant_name_idx
--   ON public.profiles (tenant_id, name);

COPY THIS

  • Perubahan schema selalu additive dulu (no rename/drop langsung)

  • Gunakan pola Add → Backfill → Switch

  • Deploy code yang backward compatible sebelum migration besar

  • Field baru awalnya nullable, constraint belakangan

  • Backfill dilakukan batch, bukan satu tembakan nuklir

  • Jalankan migration berat di off-peak + monitor

  • Pastikan query/read punya fallback (mis. COALESCE)

  • Switch logic dilakukan setelah backfill selesai dan tervalidasi

  • Cleanup kolom lama di rilis berikutnya, bukan barengan

  • Rollback plan jelas (revert code/disable switch) sebelum eksekusi

Migration aman itu kebiasaan, bukan keberuntungan. Kalau kamu treat schema change sebagai rollout bertahap, production jadi boring, dan itu justru bagus.

Di dunia nyata, “boring” itu sinonim dari “reliable”.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

Create a free website with Framer, the website builder loved by startups, designers and agencies.