Lompat ke konten
21 menit bacamateri 23 dari 96

Kembangkan skema

Normalisasi, denormalisasi selektif, periode kompatibilitas, dan cara menambah kolom atau indeks tanpa menghentikan layanan.

Ditinjau 26 Oktober 2026

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:

  1. Expand: tambah kolom currency yang boleh null (atau punya default). Kode lama tetap jalan.
  2. Tulis ganda: deploy kode yang menulis kolom lama dan baru. Tetap baca dari kolom lama.
  3. Backfill: isi currency untuk 80 juta baris lama, dalam batch kecil.
  4. Pindah baca: deploy kode yang membaca kolom baru. Verifikasi dengan perbandingan.
  5. 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 COLUMN membuat semua instance lama langsung error. Gunakan kolom baru dan expand/contract.
  • Migrasi yang memegang lock lama. ALTER TABLE yang butuh lock eksklusif menunggu transaksi panjang, dan semua query setelahnya antre di belakangnya. Satu migrasi bisa menjatuhkan layanan. Pasang lock_timeout pendek dan coba lagi.
  • Backfill tanpa pembatasan. Replika tertinggal beberapa menit, dan pembacaan dari replika menampilkan data basi.
  • Denormalisasi tanpa rekonsiliasi. item_count melenceng 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
  1. Expand: ADD COLUMN first_name TEXT, ADD COLUMN last_name TEXT, boleh null. Tanpa default berat, jadi cepat. Dua service lama tidak terpengaruh.
  2. Tulis ganda: deploy kedua service agar menulis full_name dan dua kolom baru. Karena deploy tidak bersamaan, selama jendela ini ada baris yang hanya terisi full_name. Itu diterima.
  3. Backfill: isi first_name dan last_name dari full_name untuk 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.
  4. Verifikasi: query pembanding menghitung baris tempat gabungan kolom baru tidak sama dengan full_name. Angkanya harus nol (atau daftar pengecualian yang disetujui).
  5. Pindah baca: deploy kedua service agar membaca kolom baru. Simpan penulisan ke full_name dulu agar rollback masih mungkin.
  6. Contract: setelah satu siklus rilis tanpa masalah dan tidak ada pembaca full_name (periksa log query), hentikan penulisan full_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 COLUMN dan CONCURRENTLY. 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.

Isi · System Design