KoderKoder.ai
HargaEnterpriseEdukasiUntuk investor
MasukMulai

Produk

HargaEnterpriseUntuk investor

Sumber daya

Hubungi kamiDukunganEdukasiBlog

Legal

Kebijakan privasiKetentuan penggunaanKeamananKebijakan penggunaan yang dapat diterimaLaporkan penyalahgunaan

Sosial

LinkedInTwitter
Koder.ai
Bahasa

© 2026 Koder.ai. Hak cipta dilindungi.

Beranda›Blog›Perubahan skema tanpa downtime dengan pola expand/contract
31 Agu 2025·6 menit

Perubahan skema tanpa downtime dengan pola expand/contract

Pelajari cara melakukan perubahan skema tanpa downtime dengan pola expand/contract: tambahkan kolom dengan aman, backfill bertahap, deploy kode kompatibel, lalu hapus jalur lama.

Perubahan skema tanpa downtime dengan pola expand/contract

Mengapa perubahan skema menyebabkan gangguan

Downtime dari perubahan basis data tidak selalu berupa pemadaman yang bersih dan jelas. Bagi pengguna bisa terlihat seperti halaman yang memuat selamanya, checkout yang gagal, atau aplikasi yang tiba-tiba menampilkan "ada yang salah." Bagi tim, ini muncul sebagai alert, kenaikan tingkat error, dan antrean penulisan yang gagal yang perlu dibersihkan.

Perubahan skema berisiko karena basis data dipakai bersama oleh setiap versi aplikasi yang berjalan. Saat merilis seringkali Anda memiliki kode lama dan baru hidup bersamaan (rolling deploy, banyak instance, job background). Migrasi yang tampak benar sekalipun bisa memecah salah satu versi tersebut.

Mode kegagalan umum termasuk:

  • Kode baru menulis ke kolom yang belum ada, menyebabkan error langsung.
  • Kode lama membaca kolom atau tabel yang dimigrasi ganti nama atau dihapus, menyebabkan crash setelah deploy.
  • Backfill atau pembuatan indeks menaikkan CPU atau mengunci baris, membuat permintaan biasa menjadi lambat atau time out.
  • Perubahan constraint yang "cepat" (mis. NOT NULL) memblokir penulisan saat tabel diperiksa.

Bahkan ketika kode sudah benar, rilis bisa terhambat karena masalah sebenarnya adalah waktu dan kompatibilitas antar versi. Perubahan skema tanpa downtime berpegang pada satu aturan: setiap status menengah harus aman untuk kode lama dan baru. Anda mengubah basis data tanpa memecah baca dan tulis yang ada, mengirim kode yang bisa menangani kedua bentuk, dan hanya menghapus jalur lama setelah tak ada yang bergantung padanya.

Upaya ekstra ini sepadan ketika Anda punya lalu lintas nyata, SLA ketat, atau banyak instance dan worker. Untuk alat internal kecil dengan basis data sepi, jendela maintenance terencana bisa lebih sederhana.

Expand/contract dengan kata sederhana

Kebanyakan insiden dari pekerjaan basis data terjadi karena aplikasi mengharapkan basis data berubah seketika, sementara perubahan basis data butuh waktu. Pola expand/contract menghindari itu dengan memecah satu perubahan berisiko menjadi langkah-langkah kecil yang aman.

Untuk sementara, sistem Anda mendukung dua "dialek" sekaligus. Anda perkenalkan struktur baru dulu, biarkan yang lama tetap bekerja, pindahkan data secara bertahap, lalu bersihkan.

Polanya sederhana:

  • Expand: tambahkan apa yang diperlukan (kolom, tabel, indeks) tanpa memecah aplikasi saat ini.
  • Jalankan kedua jalur: deploy kode yang bekerja dengan struktur lama dan baru sehingga versi campuran tetap berfungsi.
  • Contract: setelah semuanya menggunakan struktur baru, hapus skema lama dan kode lama.

Ini cocok dengan rolling deploy. Jika Anda memperbarui 10 server satu per satu, Anda akan sementara menjalankan versi lama dan baru bersama-sama. Expand/contract menjaga keduanya kompatibel dengan basis data yang sama selama overlap itu.

Pola ini juga membuat rollback jadi kurang menakutkan. Jika rilis baru punya bug, Anda bisa rollback aplikasi tanpa rollback basis data, karena struktur lama masih ada selama jendela expand.

Contoh: Anda ingin membagi kolom PostgreSQL full_name menjadi first_name dan last_name. Anda tambahkan kolom baru (expand), kirim kode yang bisa menulis dan membaca kedua bentuk, backfill baris lama, lalu drop full_name setelah yakin tak ada yang menggunakannya (contract).

Apa yang biasanya termasuk dalam fase "expand"

Fase expand tentang menambah opsi baru, bukan menghapus yang lama.

Langkah umum pertama adalah menambah kolom baru. Di PostgreSQL, biasanya paling aman menambahkannya sebagai nullable dan tanpa default. Menambah kolom non-null dengan default bisa memicu penulisan ulang tabel atau penguncian lebih berat, tergantung versi Postgres dan perubahan tepatnya. Urutan yang lebih aman: tambah nullable, deploy kode toleran, backfill, lalu kemudian tegakkan NOT NULL.

Indeks juga perlu perhatian. Membuat indeks biasa bisa memblokir penulisan lebih lama dari yang Anda kira. Bila memungkinkan, gunakan pembuatan indeks secara concurrent agar baca dan tulis tetap berjalan. Ini memakan waktu lebih lama, tapi menghindari lock yang menghentikan rilis.

Expand juga bisa berarti menambah tabel baru. Jika Anda bergerak dari satu kolom ke relasi many-to-many, Anda mungkin menambahkan tabel join sambil mempertahankan kolom lama. Jalur lama tetap bekerja sementara struktur baru mulai mengumpulkan data.

Dalam praktik, expand sering mencakup:

  • Menambahkan kolom nullable baru atau tabel baru di samping yang ada
  • Menambah indeks dengan cara non-blocking bila memungkinkan
  • Menggunakan feature flag untuk mengontrol kapan baca/tulis baru aktif
  • Menulis ke field lama dan baru sekaligus (dual-write) bila perlu
  • Menjaga pembacaan kompatibel ke belakang (lama, baru, atau fallback)

Setelah expand, versi aplikasi lama dan baru seharusnya bisa berjalan bersamaan tanpa kejutan.

Deploy kode yang tetap kompatibel

Sebagian besar masalah rilis terjadi di tengah: beberapa server menjalankan kode baru, yang lain masih menjalankan kode lama, sementara basis data sudah berubah. Tujuan Anda sederhana: setiap versi saat rollout harus bekerja dengan skema lama dan yang sudah di-expand.

Pendekatan umum adalah dual-write. Jika Anda menambah kolom baru, aplikasi baru menulis ke kolom lama dan kolom baru. Versi lama tetap menulis hanya ke kolom lama, yang aman karena kolom itu masih ada. Biarkan kolom baru bersifat opsional pada awalnya, dan tunda penegakan constraint ketat sampai semua penulis ter-upgrade.

Pembacaan biasanya beralih lebih hati-hati daripada penulisan. Untuk sementara, biarkan pembacaan pada kolom lama (yang Anda tahu sudah terisi penuh). Setelah backfill dan verifikasi, ubah pembacaan untuk memprioritaskan kolom baru, dengan fallback ke yang lama bila yang baru kosong.

Juga jaga agar output API stabil selama perubahan basis data. Bahkan jika Anda memperkenalkan field internal baru, hindari mengubah bentuk respons sampai semua konsumen siap (web, mobile, integrasi).

Rollout yang ramah rollback sering terlihat seperti ini:

  • Rilis 1: tambahkan kolom baru dan kirim kode yang bisa membaca data lama dan menulis kedua kolom.
  • Rilis 2: backfill baris yang ada, lalu deploy kode yang memprioritaskan pembacaan kolom baru tapi masih fallback.
  • Rilis 3: berhenti menulis kolom lama (tetapi biarkan kolom itu ada).
  • Rilis 4: hapus pembacaan lama, lalu drop kolom lama.

Ide kuncinya adalah langkah yang pertama kali tak bisa dibalik adalah menghapus struktur lama, jadi tunda sampai akhir.

Backfill data dengan aman (tanpa membebani DB)

Backfill dalam batch kecil
Buat job backfill sederhana dan atur ukuran batch tanpa memperlambat tim Anda.
Coba Gratis

Backfill adalah titik di mana banyak "perubahan skema tanpa downtime" gagal. Anda ingin mengisi kolom baru untuk baris yang ada tanpa lock lama, kueri lambat, atau lonjakan beban tak terduga.

Batching penting. Usahakan batch yang selesai cepat (detik, bukan menit). Jika setiap batch kecil, Anda bisa jeda, lanjutkan, dan menyetel job tanpa memblokir rilis.

Untuk melacak progres, gunakan cursor yang stabil. Di PostgreSQL itu sering kunci primer. Proses baris berurutan dan simpan id terakhir yang selesai, atau kerjakan rentang id. Ini menghindari full-table scan mahal saat job restart.

Berikut pola sederhana:

UPDATE my_table
SET new_col = ...
WHERE new_col IS NULL
  AND id > $last_id
ORDER BY id
LIMIT 1000;

Buat update bersyarat (mis. WHERE new_col IS NULL) sehingga job idempoten. Rerun hanya menyentuh baris yang masih perlu, yang mengurangi penulisan yang tidak perlu.

Rencanakan data baru yang datang selama backfill. Urutan yang biasa:

  • Update kode aplikasi dulu sehingga penulisan baru juga mengisi field baru.
  • Backfill baris historis dalam batch.
  • Jalankan loop catch-up singkat yang memeriksa baris terbaru.
  • Jika perlu, tambahkan guardrail (seperti trigger atau default) untuk mencegah NULL baru.

Backfill yang baik itu membosankan: stabil, terukur, dan mudah dijeda jika database mulai panas.

Memverifikasi migrasi benar-benar selesai

Momen paling berisiko bukan menambah kolom baru. Melainkan memutuskan Anda bisa mengandalkannya.

Sebelum pindah ke contract, buktikan dua hal: data baru lengkap, dan produksi telah membacanya dengan aman.

Mulai dengan pemeriksaan kelengkapan yang cepat dan dapat diulang:

  • Pastikan kolom baru tidak memiliki NULL tak terduga.
  • Bandingkan berapa banyak baris yang eligible vs berapa yang sudah diisi.
  • Spot-check beberapa ID dan bandingkan nilai lama vs baru.
  • Uji kasus tepi (string kosong, nol, rekaman sangat lama).
  • Jalankan ulang pemeriksaan yang sama nanti untuk memastikan tidak ada drift.

Jika Anda dual-writing, tambahkan pemeriksaan konsistensi untuk menangkap bug senyap. Misalnya, jalankan kueri setiap jam yang menemukan baris di mana old_value <> new_value dan alert jika tidak nol. Ini sering cara tercepat menemukan bahwa satu penulis masih hanya mengupdate kolom lama.

Pantau sinyal produksi dasar saat migrasi berjalan. Jika waktu kueri atau lock waits melonjak, bahkan kueri verifikasi Anda yang "aman" bisa menambah beban. Pantau tingkat error untuk jalur kode yang membaca kolom baru, terutama segera setelah deploy.

Berapa lama menyimpan kedua jalur? Cukup lama untuk melewati setidaknya satu siklus rilis penuh dan satu rerun backfill. Banyak tim memakai 1–2 minggu, atau sampai yakin tidak ada versi aplikasi lama yang masih berjalan.

Fase Contract: menghapus jalur lama

Contract adalah saat tim sering gugup karena terasa seperti titik tanpa kembali. Jika expand dilakukan dengan benar, contract sebagian besar adalah pembersihan, dan Anda masih bisa melakukannya dalam langkah kecil berisiko rendah.

Pilih waktu dengan hati-hati. Jangan drop apa pun tepat setelah backfill selesai. Beri setidaknya satu siklus rilis penuh sehingga job tertunda dan kasus tepi punya waktu muncul.

Urutan contract yang aman biasanya:

  • Berhenti dual-write dan konfirmasi penulisan baru hanya ke kolom baru.
  • Hapus pembacaan lama di aplikasi sehingga fallback hilang.
  • Hapus kode mati, feature flag, dan job background yang mereferensi skema lama.
  • Hapus trigger sementara, job sinkronisasi, atau view kompatibilitas.
  • Drop indeks dan constraint lama, lalu drop kolom lama.

Jika bisa, bagi contract ke dua rilis: satu yang menghapus referensi kode (dengan logging ekstra), dan yang lain yang menghapus objek basis data. Pemisahan itu memudahkan rollback dan troubleshooting.

Spesifik PostgreSQL penting di sini. Drop kolom sebagian besar adalah perubahan metadata, tapi tetap mengambil lock ACCESS EXCLUSIVE sebentar. Rencanakan saat tenang dan buat migrasi cepat. Jika Anda menambah indeks ekstra, pilih DROP INDEX CONCURRENTLY untuk menghindari memblokir penulisan (itu tidak bisa dijalankan dalam transaction block, jadi tooling migrasi Anda harus mendukungnya).

Kesalahan umum dan jebakan

Rencanakan perubahan skema yang lebih aman
Ubah rencana expand-contract Anda menjadi tugas dan checkpoint konkret sebelum menyentuh produksi.
Mulai Gratis

Migrasi tanpa downtime gagal ketika basis data dan aplikasi berhenti sepakat tentang apa yang diperbolehkan. Pola ini bekerja hanya jika setiap status menengah aman untuk kode lama dan baru.

Jebakan yang memecah produksi

Kesalahan ini sering muncul:

  • Menambahkan NOT NULL terlalu awal, sementara versi aplikasi lama masih bisa menulis baris tanpa field baru.
  • Backfill tabel besar dalam satu transaksi, yang bisa memegang lock, membengkakkan tabel, dan menyebabkan timeout.
  • Menganggap default itu gratis. Di PostgreSQL, beberapa default memicu penulisan ulang tabel.
  • Beralih baca ke kolom baru sebelum penulisan pasti mengisinya.
  • Lupa penulis dan pembaca lain (cron job, worker, export, query reporting).

Skenario realistis: Anda mulai menulis full_name dari API, tapi job background yang membuat user masih hanya mengisi first_name dan last_name. Job itu berjalan malam hari, memasukkan baris dengan full_name = NULL, dan kemudian kode lain mengasumsikan full_name selalu ada.

Cara menghindari terjebak di tengah migrasi

Anggap setiap langkah seperti rilis yang dapat berlangsung berhari-hari:

  • Biarkan kolom baru nullable selama transisi, dan tegakkan "required" di kode terlebih dahulu.
  • Backfill dalam batch kecil dengan jeda, dan pantau beban DB.
  • Buat kode toleran: baca kedua jalur, tulis kedua jalur bila diperlukan, tangani nilai yang hilang.
  • Audit setiap tempat yang menyentuh tabel, termasuk worker dan reporting.

Daftar periksa cepat sebelum setiap rilis

Checklist yang bisa diulang menjaga Anda dari mengirim kode yang hanya bekerja di satu status basis data.

Sebelum deploy, konfirmasi basis data sudah memiliki bagian expand (kolom/tabel baru, indeks dibuat dengan cara low-lock). Lalu pastikan aplikasi toleran: harus bekerja terhadap bentuk lama, bentuk yang di-expand, dan kondisi setengah-backfilled.

Jaga checklist singkat:

  • Expansion ada: objek skema baru ada dan ditambahkan dengan cara low-lock.
  • Kompatibilitas nyata: aplikasi bekerja dengan skema lama dan yang diperluas, termasuk worker dan path admin.
  • Backfill terkendali: batch kecil, bisa dijeda, dengan metrik progres dasar.
  • Rencana switch baca: Anda tahu persis kapan baca pindah, dan cara rollback jika hasil salah.
  • Contract ditunda: tunggu setidaknya satu atau dua siklus rilis sebelum drop objek lama.

Sebuah migrasi dianggap selesai ketika baca menggunakan data baru, tulis tidak lagi mempertahankan data lama, dan Anda sudah memverifikasi backfill dengan setidaknya satu cek sederhana (count atau sampling).

Contoh realistis: ganti kolom tanpa downtime

Prototype dengan PostgreSQL cepat
Spin up aplikasi dengan Postgres dan iterasikan skema dengan aman saat Anda mengirimkan perubahan.
Bangun Sekarang

Misalnya Anda punya tabel PostgreSQL customers dengan kolom phone yang menyimpan nilai berantakan (berbagai format, kadang kosong). Anda ingin menggantinya dengan phone_e164, tapi tidak bisa memblokir rilis atau menurunkan aplikasi.

Urutan expand/contract yang bersih terlihat seperti ini:

  • Expand: tambahkan phone_e164 sebagai nullable, tanpa default, dan tanpa constraint berat dulu.
  • Deploy kompatibel: update kode agar menulis phone dan phone_e164, tapi tetap membaca dari phone supaya pengguna tidak melihat perubahan.
  • Backfill: konversi baris yang ada dalam batch kecil (mis. 1.000 per batch).
  • Switch baca: deploy kode yang membaca phone_e164 terlebih dahulu, dan fallback ke phone jika masih NULL.
  • Contract: setelah yakin semuanya menggunakan phone_e164, hapus fallback, drop phone, lalu tambahkan constraint lebih ketat jika masih diperlukan.

Rollback tetap sederhana ketika setiap langkah kompatibel ke belakang. Jika switch baca menyebabkan masalah, rollback aplikasi dan basis data masih punya kedua kolom. Jika backfill menyebabkan lonjakan beban, jeda job, kecilkan ukuran batch, dan lanjutkan nanti.

Untuk menjaga tim tetap selaras, dokumentasikan rencana di satu tempat: SQL tepatnya, rilis mana yang mengubah pembacaan, bagaimana mengukur penyelesaian (mis. persentase non-NULL phone_e164), dan siapa pemilik setiap langkah.

Langkah selanjutnya: buat ini dapat diulang

Expand/contract bekerja terbaik ketika terasa rutin. Tulis runbook singkat yang bisa dipakai ulang oleh tim Anda setiap kali melakukan perubahan skema, idealnya satu halaman dan cukup spesifik sehingga rekan baru bisa mengikutinya.

Template praktis mencakup:

  • Expand (migrasi tepat)
  • Perubahan kode (apa yang harus tetap kompatibel ke belakang, dan di mana dual-read atau dual-write dipakai)
  • Backfill (ukuran batch, batas laju, jeda/lanjut)
  • Verifikasi (kueri dan metrik yang membuktikan kebenaran)
  • Contract (apa yang dihapus, dan kapan)

Tentukan kepemilikan di muka. "Semua pikir orang lain yang akan melakukan contract" adalah alasan kolom lama dan feature flag hidup berbulan-bulan.

Bahkan jika backfill berjalan online, jadwalkan saat lalu lintas lebih rendah. Lebih mudah menjaga batch kecil, memantau beban DB, dan berhenti cepat jika latensi naik.

Jika Anda membangun dan deploy dengan Koder.ai (koder.ai), Planning Mode bisa menjadi cara berguna untuk memetakan fase dan checkpoint sebelum menyentuh produksi. Aturan kompatibilitas yang sama tetap berlaku, tetapi menuliskan langkah membuatnya lebih sulit untuk melewatkan bagian membosankan yang mencegah gangguan.

Pertanyaan umum

Mengapa perubahan skema bisa menyebabkan gangguan meskipun SQL terlihat benar?

Karena basis data Anda dipakai bersama oleh semua versi aplikasi yang berjalan. Selama rolling deploy dan job background, kode lama dan baru bisa berjalan bersamaan, dan migrasi yang mengubah nama, menghapus kolom, atau menambah constraint bisa memecah versi mana pun yang tidak dibuat untuk status skema itu.

Apa arti sebenarnya dari “perubahan skema tanpa downtime”?

Artinya Anda merancang migrasi sehingga setiap status sementara basis data aman untuk kode lama dan baru. Anda menambahkan struktur baru lebih dulu, menjalankan kedua jalur (lama dan baru) untuk sementara, lalu menghapus struktur lama hanya setelah tidak ada yang bergantung padanya.

Apa perbedaan antara fase expand dan contract?

Expand menambahkan kolom, tabel, atau indeks baru tanpa menghapus apa pun yang dibutuhkan aplikasi saat ini. Contract adalah fase pembersihan di mana Anda menghapus kolom lama, baca/tulis lama, dan logika sinkronisasi sementara setelah jalur baru terbukti bekerja sepenuhnya.

Apa cara paling aman untuk menambahkan kolom baru di PostgreSQL?

Menambahkan kolom nullable tanpa default biasanya langkah paling aman karena menghindari penguncian berat dan menjaga kode lama tetap bekerja. Setelah itu deploy kode yang tahan terhadap kolom yang hilang atau NULL, backfill bertahap, dan baru setelahnya ketatkan constraint seperti NOT NULL.

Kapan saya harus menggunakan dual-write, dan apa fungsinya?

Gunakan ketika versi aplikasi baru menulis ke field baru sementara versi lama masih menulis ke field lama. Versi baru menulis ke keduanya sehingga data tetap konsisten saat masih ada instance lama dan job yang hanya mengenal field lama.

Bagaimana cara backfill data tanpa memperlambat produksi?

Lakukan backfill dalam batch kecil yang selesai cepat, dan buat setiap batch idempoten supaya rerun hanya memperbarui baris yang masih perlu. Pantau waktu kueri, lock waits, dan lag replikasi, dan siap untuk menjeda atau memperkecil ukuran batch jika database mulai panas.

Bagaimana saya bisa memverifikasi migrasi benar-benar selesai sebelum menghapus apa pun?

Periksa kelengkapan, misalnya berapa baris yang masih NULL di kolom baru. Lakukan pengecekan konsistensi yang membandingkan nilai lama dan baru pada sampel (atau secara terus-menerus jika murah), dan pantau error produksi setelah deploy untuk mendeteksi jalur kode yang masih menggunakan skema lama.

Langkah migrasi mana yang paling sering memecah produksi?

NOT NULL atau constraint baru dapat memblokir penulisan saat tabel divalidasi, dan pembuatan indeks biasa bisa memegang lock lebih lama dari perkiraan. Rename dan drop juga berisiko karena kode lama mungkin masih mereferensi nama lama selama rolling deploy.

Kapan aman melakukan langkah contract dan menghapus kolom lama?

Hanya setelah Anda berhenti menulis ke field lama, mengalihkan pembacaan ke field baru tanpa fallback, dan menunggu cukup lama untuk yakin tidak ada versi aplikasi lama atau worker yang masih berjalan. Banyak tim memisahkan ini sebagai rilis terpisah sehingga rollback tetap sederhana.

Apakah saya selalu perlu expand/contract, atau cukup lakukan maintenance window?

Jika Anda bisa menerima jendela maintenance dan lalu lintas rendah, migrasi sekali jalan mungkin cukup. Jika Anda punya pengguna nyata, banyak instance, worker background, atau SLA, expand/contract biasanya sepadan karena menjaga rollout dan rollback lebih aman; di Koder.ai Planning Mode, menuliskan fase dan cek poin sebelumnya membantu menghindari melewatkan langkah “membosankan” yang mencegah gangguan.

Daftar isi
Mengapa perubahan skema menyebabkan gangguanExpand/contract dengan kata sederhanaApa yang biasanya termasuk dalam fase "expand"Deploy kode yang tetap kompatibelBackfill data dengan aman (tanpa membebani DB)Memverifikasi migrasi benar-benar selesaiFase Contract: menghapus jalur lamaKesalahan umum dan jebakanDaftar periksa cepat sebelum setiap rilisContoh realistis: ganti kolom tanpa downtimeLangkah selanjutnya: buat ini dapat diulangPertanyaan umum
Bagikan
Koder.ai
Buat aplikasi sendiri dengan Koder hari ini!

Cara terbaik untuk memahami kekuatan Koder adalah melihatnya sendiri.

Mulai GratisPesan Demo