# Migrasi Database Tanpa Downtime dengan Prisma

> Terapkan perubahan skema tanpa membuat aplikasi offline, menggunakan pola expand-contract bersama Prisma: backfill, jendela dual-write, dan menghapus kolom lama dengan aman.

URL: https://www.matthewswong.com/id/blog/zero-downtime-database-migrations-prisma/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-07-02
- Updated: 2026-07-02
- Category: Backend

Deploy yang paling menakutkan bagi tim backend mana pun adalah migrasi skema. Menambah fitur risikonya rendah. Mengubah bentuk tabel yang sedang dihantam traffic produksi adalah permainan yang sama sekali berbeda, karena untuk sementara waktu, kode aplikasi lama dan kode aplikasi baru harus sama-sama bisa bekerja terhadap database yang sama, dan satu statement ALTER yang salah bisa menjatuhkan seluruh layanan.

Tulisan ini membahas pola expand-contract untuk Prisma dan Postgres: cara menambah kolom tanpa mengunci tabel, cara melakukan backfill jutaan baris tanpa membuat connection pool kehabisan koneksi, cara menjalankan jendela dual-write yang aman, dan cara menghapus kolom lama belakangan tanpa ada yang menyadarinya. Setiap perintah di sini bisa langsung dijalankan hari ini dengan Prisma Migrate.

## Kenapa Satu Statement ALTER Bisa Menjatuhkan Produksi

Migrasi yang naif mengubah nama kolom, mengubah tipenya, atau menambah constraint NOT NULL dalam satu langkah, lalu merilis kode aplikasi baru yang berasumsi perubahan tersebut sudah aktif. Masalahnya, deployment tidak pernah instan. Selalu ada jendela waktu di mana instance server lama masih berjalan terhadap skema baru, atau instance baru berjalan terhadap skema lama, terutama pada rolling deploy di belakang load balancer. Jika migrasi dan perubahan kode tidak dipisah, jendela waktu itu menghasilkan query yang gagal, error 500, atau bahkan kerusakan data secara diam-diam. Postgres memperbesar risiko ini: beberapa bentuk ALTER TABLE mengambil lock ACCESS EXCLUSIVE, dan meskipun penambahan kolom biasa umumnya hanya perubahan metadata yang cepat, menambah kolom dengan default yang volatile, constraint NOT NULL yang butuh scan validasi penuh, atau mengubah tipe kolom bisa memaksa Postgres menulis ulang seluruh tabel beserta indeksnya, memblokir setiap pembaca dan penulis lain sampai proses itu selesai.

## Pola Expand-Contract, Dalam Satu Kalimat

Jangan pernah menghapus atau mengubah fungsi sesuatu yang masih dipakai aplikasi yang sedang berjalan dalam deploy yang sama dengan perubahan itu: perluas skema dengan menambah struktur baru berdampingan dengan yang lama, pindahkan data dan kode secara bertahap lewat langkah-langkah kecil yang bisa dibatalkan, dan baru persempit dengan menghapus struktur lama setelah tidak ada lagi yang membacanya. Tabel di bawah ini menunjukkan garis waktu rollout yang diikuti tulisan ini, menggunakan tabel Invoice fiktif yang memigrasikan kolom amount dari integer sen ke kolom Decimal yang layak.

1. Expand

Tambahkan kolom nullable baru lewat migrasi yang cepat dan aman dari lock

Ya

2. Backfill

Isi kolom baru secara bertahap dalam batch kecil dari data yang sudah ada

Ya

3. Dual-write

Rilis kode yang menulis ke kedua kolom; pembacaan masih memakai kolom lama

Ya

4. Cutover

Rilis kode yang membaca dan menulis hanya ke kolom baru

Ya, data lama tidak tersentuh

5. Contract

Hapus kolom lama pada rilis terpisah di kemudian hari

Tidak bisa rollback setelah titik ini

## Langkah 1: Perluas Skema Tanpa Mengunci Tabel

Aturan emas fase expand adalah: tambahkan kolomnya, tapi jangan sematkan constraint NOT NULL atau default volatile dalam statement yang sama. Postgres bisa menambah kolom nullable biasa sebagai perubahan metadata murni dalam hitungan milidetik, karena tidak perlu menyentuh satu pun baris yang sudah ada. Begitu Anda menambahkan NOT NULL atau nilai default yang bukan konstanta tetap, Postgres harus memvalidasi atau menulis ulang setiap baris, dan penulisan ulang itu mengambil lock eksklusif selama seluruh prosesnya. Workflow default Prisma Migrate menjalankan migrasi secara otomatis, tapi untuk hal yang sensitif sebaiknya gunakan flag create-only supaya Anda bisa meninjau dan mengedit SQL yang dihasilkan sebelum menyentuh database sama sekali. Ini memberi kesempatan untuk membuang constraint NOT NULL yang ditambahkan CLI, atau memecah satu migrasi menjadi dua statement terpisah yang lebih aman.

> Menambahkan constraint NOT NULL, default volatile seperti fungsi timestamp, atau kolom generated semuanya memaksa Postgres menulis ulang seluruh tabel beserta indeksnya di bawah lock ACCESS EXCLUSIVE. Pada tabel besar, penulisan ulang itu bisa memakan waktu bermenit-menit, dan selama itu setiap pembacaan dan penulisan ke tabel tersebut akan terblokir. Selalu tambahkan kolom sebagai nullable dulu, backfill datanya, dan baru tambahkan NOT NULL setelah setiap baris sudah memiliki nilai.

## Langkah 2: Backfill dalam Batch Kecil yang Bisa Dilanjutkan

Setelah kolom baru ada, Anda perlu mengisinya untuk setiap baris yang sudah ada tanpa berebut koneksi atau lock dengan traffic produksi. Satu statement UPDATE tunggal di tabel dengan jutaan baris akan menahan lock pada setiap baris yang disentuhnya sampai transaksi selesai, yang bisa menghentikan query lain selama seluruh proses berjalan. Pendekatan yang lebih aman adalah skrip yang menyusuri tabel dalam batch berurutan, menggunakan primary key sebagai cursor, dan melakukan commit tiap batch dalam transaksi pendeknya sendiri. Ini membuat jendela lock tiap langkah kecil, memungkinkan pencatatan progres agar bisa dilanjutkan setelah crash, dan memungkinkan Anda mengatur kecepatan jika terlihat replication lag atau tekanan pada connection pool mulai meningkat. Jalankan sebagai job terpisah, bukan di dalam request handler, dan arahkan query verifikasinya ke read replica agar tidak pernah berebut dengan pembacaan produksi.

> Tambahkan jeda singkat di antara batch. Loop rapat yang melakukan backfill secepat mungkin yang diizinkan database bisa membuat connection pool kehabisan koneksi dan memicu lonjakan replication lag pada read replica, meskipun setiap transaksi individual kecil dan cepat.

## Langkah 3: Jendela Dual-Write

Sebelum bisa mempercayai kolom baru, rilis versi di mana kode aplikasi menulis ke kolom lama dan baru pada setiap insert dan update, sementara pembacaan masih memakai kolom lama. Inilah jaring pengamannya: jika backfill melewatkan sebuah kasus khusus atau kolom baru punya bug halus, kolom lama tetap menjadi sumber kebenaran dan tidak ada yang rusak dari sisi pengguna. Jaga jendela ini sesingkat mungkin secara wajar, tapi cukup panjang untuk menangkap satu siklus bisnis penuh dari penulisan data, termasuk job batch atau webhook mana pun yang menulis ke tabel di luar jalur request normal.

- Setiap jalur kode yang menulis ke tabel, termasuk job latar belakang, webhook, dan alat admin, harus diperbarui agar menulis ke kedua kolom sebelum Anda mengandalkan kolom baru.
- Tambahkan query rekonsiliasi yang secara berkala membandingkan nilai kolom lama dan baru pada sampel baris, sehingga bug tersembunyi pada logika dual-write muncul sebelum cutover, bukan sesudahnya.
- Pertahankan rilis dual-write selama minimal satu siklus penuh dari job berulang Anda yang paling lambat, seperti proses billing bulanan, sebelum berpindah ke cutover.

## Langkah 4 dan 5: Cutover, Lalu Contract

Setelah query rekonsiliasi bersih selama jendela waktu yang Anda pilih, rilis versi cutover: kode aplikasi membaca dan menulis hanya ke kolom baru, dan kolom lama menjadi beban mati, bukan lagi dependensi. Ini masih bisa dibatalkan, karena kolom lama masih ada dengan nilai terakhirnya, sehingga Anda bisa melakukan rollback rilis tanpa migrasi data. Baru setelah cutover stabil di produksi Anda melakukan contract: menghapus kolom lama dalam migrasi khusus tersendiri, dirilis terpisah dari perubahan kode apa pun. Memisahkannya penting karena menghapus kolom bersifat destruktif dan tidak boleh digabung dengan perubahan skema atau fitur lain yang tidak berkaitan.

1. Pastikan lewat metrik atau log bahwa tidak ada yang membaca dari kolom lama selama setidaknya satu siklus deploy penuh.
2. Buat migrasi draf dengan flag create-only agar Anda bisa meninjau statement DROP COLUMN yang tepat sebelum dijalankan.
3. Ambil backup atau snapshot point-in-time recovery segera sebelum menerapkan penghapusan, karena langkah ini tidak bisa dibatalkan tanpa memulihkan data.
4. Terapkan migrasi dengan perintah deploy standar di pipeline CI/CD Anda, lalu hapus field yang sudah tidak dipakai dari skema Prisma dalam commit yang sama.

Inti dari pola ini adalah pada setiap langkah kecuali yang terakhir, Anda bisa membatalkan rilis aplikasi tanpa menyentuh database sama sekali. Itulah yang membuat expand-contract benar-benar tanpa downtime, bukan sekadar downtime yang lebih singkat.

## Bagaimana Ini Dipetakan ke Workflow Prisma Migrate Sesungguhnya

Dalam praktiknya ini menjadi lima pull request kecil, membosankan, dan bisa ditinjau satu per satu, alih-alih satu pull request besar yang berisiko: tambahkan kolom nullable, jalankan skrip backfill sebagai langkah deploy atau job terjadwal, rilis perubahan kode dual-write, rilis perubahan kode cutover, dan akhirnya hapus kolom lama. Setiap tahap di-deploy secara independen lewat perintah migrate deploy yang sama yang sudah dijalankan pipeline CI/CD Anda, jadi tidak ada yang aneh dari proses rilisnya sendiri, hanya disiplin untuk tidak menggabungkan lima langkah aman menjadi satu langkah berisiko.

> Perlakukan setiap migrasi yang menyentuh tabel dengan traffic produksi yang berarti sebagai kandidat pola ini secara default, bukan sebagai kasus khusus yang baru dipakai ketika sesuatu sudah rusak. Pull request tambahan itu murah; insiden akibat penulisan ulang tabel di jam kerja tidak.

## Sumber

- Dokumentasi Prisma — Migrasi data dengan pola expand and contract
- Dokumentasi Prisma — Menyesuaikan migrasi (workflow create-only)
- Dokumentasi PostgreSQL — ALTER TABLE (level lock dan kondisi penulisan ulang tabel)

## Source links

- https://www.prisma.io/docs/guides/data-migration-with-expand-and-contract
- https://www.prisma.io/docs/orm/prisma-migrate/workflows/customizing-migrations
- https://www.postgresql.org/docs/current/sql-altertable.html

## FAQ

### Apa itu pola expand-contract untuk migrasi database?

Expand-contract adalah strategi migrasi di mana Anda menambahkan struktur skema baru berdampingan dengan yang lama, memindahkan data dan kode aplikasi secara bertahap lewat langkah-langkah kecil yang bisa dibatalkan, dan baru menghapus struktur lama setelah tidak ada lagi yang bergantung padanya. Pola ini menghindari downtime yang muncul akibat mengubah bentuk kolom dalam deploy yang sama saat kode aplikasi mengasumsikan perubahan itu sudah aktif.

### Kenapa menambah kolom NOT NULL mengunci tabel Postgres?

Menambah kolom nullable biasa adalah perubahan metadata semata dan selesai dalam hitungan milidetik. Menambah constraint NOT NULL atau default volatile memaksa Postgres memvalidasi atau menulis ulang setiap baris yang ada, dan penulisan ulang itu menahan lock ACCESS EXCLUSIVE selama seluruh prosesnya, memblokir setiap pembacaan dan penulisan lain ke tabel tersebut sampai selesai.

### Bagaimana cara backfill tabel besar tanpa menguncinya di Prisma?

Tulis skrip mandiri yang menyusuri tabel dalam batch kecil berurutan menggunakan primary key sebagai cursor, melakukan commit tiap batch dalam transaksi pendeknya sendiri, bukan satu UPDATE raksasa. Tambahkan jeda singkat di antara batch dan arahkan query verifikasi ke read replica agar backfill tidak pernah berebut lock atau koneksi dengan traffic produksi.

### Bisakah saya rollback setelah menghapus kolom dalam sebuah migrasi?

Tidak, kecuali dengan memulihkan dari backup atau snapshot point-in-time recovery. Menghapus kolom adalah satu-satunya langkah dalam pola expand-contract yang tidak bisa dibatalkan lewat rollback aplikasi biasa, itulah sebabnya langkah ini harus selalu menjadi migrasi khusus tersendiri yang dirilis terpisah dari perubahan kode apa pun, dengan backup baru yang diambil segera sebelumnya.

### Bagaimana cara mengedit migrasi Prisma sebelum dijalankan ke database?

Jalankan prisma migrate dev dengan flag create-only untuk menghasilkan file migration.sql tanpa menerapkannya. Anda kemudian bisa membuka file itu, menghapus constraint yang otomatis ditambahkan CLI, memecahnya menjadi statement yang lebih aman, atau menambahkan SQL kustom seperti RENAME COLUMN, sebelum menjalankan prisma migrate dev lagi untuk menerapkan versi yang sudah diedit.

---

- HTML version: https://www.matthewswong.com/id/blog/zero-downtime-database-migrations-prisma/
- English: https://www.matthewswong.com/en/blog/zero-downtime-database-migrations-prisma/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
