Kembangkan skema
Normalisasi, denormalisasi selektif, periode kompatibilitas, dan cara menambah kolom atau indeks tanpa menghentikan layanan.
Pertanyaan
Bagaimana mengubah skema yang sedang dipakai tanpa downtime dan tanpa merusak kode yang lama?
Setelah baca ini, kamu bisa memutuskan kapan denormalisasi layak, memecah perubahan skema jadi langkah yang kompatibel mundur, dan membuat indeks di tabel besar tanpa memblokir tulisan.
Prasyarat dan contoh
Kamu sudah bisa memodelkan skema dari pola akses (M04.01), merancang indeks (M04.03), dan membaca rencana query (M04.04).
Tabel orders sekarang menyimpan total_cents. Produk baru butuh multi mata uang, jadi kolom
currency harus ada. Tabelnya berisi 80 juta baris, melayani 200 tulis per detik, dan ada tiga
service yang menulis ke sana. Perubahan "tambah kolom lalu deploy" gagal begitu satu service
belum di-deploy dan mulai menulis tanpa kolom baru.
Skema yang hidup berubah dalam kondisi ada kode lama dan kode baru berjalan bersamaan. Itu sebabnya urutan langkah penting.
Mekanisme
Normalisasi dan denormalisasi selektif. Normalisasi menyimpan tiap fakta di satu tempat,
sehingga update cuma menyentuh satu baris dan nggak ada data kontradiktif. Denormalisasi
menyalin fakta demi baca yang lebih murah. Contoh: orders.item_count yang di-update tiap item
berubah, supaya daftar order nggak perlu COUNT ke order_items untuk 20 baris per halaman.
Lakukan denormalisasi kalau: (1) ada query kritis yang terbukti mahal di rencana, (2) kamu tahu siapa yang menjaga salinan tetap benar, dan (3) kamu punya cara memeriksa dan memperbaiki penyimpangan. Salinan tanpa pemilik cepat atau lambat berbeda dari sumbernya.
Pola expand/contract (periode kompatibilitas). Ubah dalam langkah yang masing-masing aman untuk kode lama maupun baru:
- Expand: tambah kolom
currencyyang boleh null (atau punya default). Kode lama tetap jalan. - Tulis ganda: deploy kode yang menulis kolom lama dan baru. Tetap baca dari kolom lama.
- Backfill: isi
currencyuntuk 80 juta baris lama, dalam batch kecil. - Pindah baca: deploy kode yang membaca kolom baru. Verifikasi dengan perbandingan.
- Contract: setelah semua versi lama dimatikan dan sudah lewat satu siklus rilis, hapus
kolom lama atau pasang
NOT NULL.
Tiap langkah bisa di-rollback tanpa kehilangan data sampai langkah terakhir. Langkah terakhir irreversible, jadi tunda sampai yakin.
Backfill yang aman. Satu UPDATE atas 80 juta baris memegang lock dan membengkakkan WAL.
Lakukan per batch, misalnya 5.000 baris per transaksi dengan jeda, sambil memantau lag replika.
Pada 5.000 baris per batch dan 20 batch per detik, 80 juta baris selesai dalam sekitar 800 detik.
Dalam praktik jauh lebih lambat karena jeda dan beban lain, dan itu wajar.
Indeks tanpa downtime. Di PostgreSQL, CREATE INDEX biasa memblokir tulis ke tabel selama
pembuatan. CREATE INDEX CONCURRENTLY tidak memblokir tulis, tapi butuh dua kali pemindaian
tabel, lebih lambat, tidak bisa di dalam transaksi, dan bisa gagal meninggalkan indeks INVALID
yang harus dihapus. Mesin lain punya mekanisme online sendiri dengan batasan berbeda, jadi baca
dokumentasi versi yang kamu pakai.
Menambah kolom itu tidak selalu murah. Di PostgreSQL 11 ke atas, menambah kolom dengan default konstan hanya mengubah metadata. Di mesin atau versi lain, bisa menulis ulang seluruh tabel. Jangan berasumsi, uji di salinan berukuran produksi.
Saat rancangan ini gagal
- Rename dalam satu langkah.
ALTER TABLE ... RENAME COLUMNmembuat semua instance lama langsung error. Gunakan kolom baru dan expand/contract. - Migrasi yang memegang lock lama.
ALTER TABLEyang butuh lock eksklusif menunggu transaksi panjang, dan semua query setelahnya antre di belakangnya. Satu migrasi bisa menjatuhkan layanan. Pasanglock_timeoutpendek dan coba lagi. - Backfill tanpa pembatasan. Replika tertinggal beberapa menit, dan pembacaan dari replika menampilkan data basi.
- Denormalisasi tanpa rekonsiliasi.
item_countmelenceng setelah satu bug. Tidak ada pemeriksaan berkala, jadi baru ketahuan dari keluhan pelanggan. - Contract terlalu cepat. Kolom lama dihapus sebelum service ketiga di-deploy. Service itu baru ketahuan saat error produksi.
Contoh tandingan: tabel 5.000 baris yang dipakai satu service tidak butuh expand/contract. Tambah kolom dan deploy bersamaan sudah cukup, selama downtime beberapa detik bisa diterima. Prosedur berat untuk risiko yang nggak ada juga biaya.
Latihan
Tabel users (30 juta baris) punya kolom full_name. Kamu ingin memecahnya jadi first_name dan
last_name. Ada dua service yang membaca dan menulis full_name, dan deploy-nya tidak
bersamaan. Tulis rencana langkahnya.
Lihat pembahasan
- Expand:
ADD COLUMN first_name TEXT, ADD COLUMN last_name TEXT, boleh null. Tanpa default berat, jadi cepat. Dua service lama tidak terpengaruh. - Tulis ganda: deploy kedua service agar menulis
full_namedan dua kolom baru. Karena deploy tidak bersamaan, selama jendela ini ada baris yang hanya terisifull_name. Itu diterima. - Backfill: isi
first_namedanlast_namedarifull_nameuntuk baris yang kosong, 5.000 baris per batch, pantau lag replika. Aturan pemecahan nama harus ditentukan eksplisit, karena nama tidak selalu dua kata. Baris yang ambigu ditandai untuk ditinjau. - Verifikasi: query pembanding menghitung baris tempat gabungan kolom baru tidak sama dengan
full_name. Angkanya harus nol (atau daftar pengecualian yang disetujui). - Pindah baca: deploy kedua service agar membaca kolom baru. Simpan penulisan ke
full_namedulu agar rollback masih mungkin. - Contract: setelah satu siklus rilis tanpa masalah dan tidak ada pembaca
full_name(periksa log query), hentikan penulisanfull_name, lalu hapus kolomnya di rilis terpisah.
Jawaban bagus menyebut jendela di mana kode lama dan baru hidup bersamaan, batch backfill, verifikasi dengan angka, dan menunda langkah yang tidak bisa dibatalkan.
Terkait
- M01.06 Kenali batas infrastruktur mengingatkan memilih langkah paling murah yang memadai, termasuk saat memilih seberapa berat prosedur migrasi.
- M04.03 membahas indeks yang dibuat lewat prosedur online di sini.
- M05 Transaksi dan konkurensi bahas lock dan isolasi yang membuat migrasi bisa memblokir.
- Lab L04 menyertakan skenario perubahan skema.
Catatan
- Asumsi: PostgreSQL 12 ke atas untuk perilaku
ADD COLUMNdanCONCURRENTLY. Mesin lain berbeda. - Yang nggak dibahas: alat migrasi tertentu, perubahan skema di store tanpa skema, dan migrasi antar-mesin (misalnya pindah vendor).
- Estimasi waktu backfill kasar dan mengabaikan beban lain.
- Terakhir ditinjau: 26 Oktober 2026.